Effective Java 读书笔记

来源:互联网 发布:淘宝联盟自己买自己的 编辑:程序博客网 时间:2024/04/30 03:00

1、当有多个参数时,使用构建器模式

常用的场景是,查询时,有多个参数。因此构建查询参数对象时,考虑使用Builder模式。

public class QueryBuilder {    //查询的每个参数,不需要set方法    private  String userName;    private String mobile;    private int cityId;    private int pageNum;    private int pageSize;    //提供一个Builder实例    public static Builder newBuilder(){        return new Builder();    }    //共有的public 静态static内部类    public static class Builder{        private  String userName;        private String mobile;        private int cityId;        private int pageNum;        private int pageSize;        //每个方法都是公有的         public Builder withUserName(String userName){            this.userName = userName;            return this;        }        public Builder withMobile(String mobile){            this.mobile = mobile;            return this;        }        public Builder withCityId(int cityId){            this.cityId = cityId;            return this;        }        public Builder withPageNum(int pageNum){            this.pageNum = pageNum;            return this;        }        public Builder withPageSize(int pageSize){            this.pageSize = pageSize;            return this;        }        //最后提供一个build方法,返回查询类        public QueryBuilder build(){            return new QueryBuilder(this);        }    }    //查询类构造方法私有,接收一个Builder参数    private QueryBuilder(Builder builder){        userName = builder.userName;        mobile = builder.mobile;        cityId = builder.cityId;        pageNum = builder.pageNum;        pageSize = builder.pageSize;        //可以根据需要提供一个boolean hasQuery字段,用于判断是否有查询条件        //没有查询条件时,返回所有值        /*hasQuery = StringUtils.isNotEmpty(userName)                || StringUtils.isNotEmpty(mobile)                || cityId > 0;       */    }}    public String getUserName() {        return userName;    }    public String getMobile() {        return mobile;    }    public int getCityId() {        return cityId;    }    public int getPageNum() {        return pageNum;    }    public int getPageSize() {        return pageSize;    }    public boolean isHasQuery() {        return hasQuery;    }
//使用QueryBuilder.Builder builder = QueryBuilder.newBuilder();QueryBuilder query = builder.withUserName("name1").withMobile("13456463216")                            .withCityId(1).withPageNum(0).withPageSize(10)                            .build();

2、使用私有构造器或者枚举类型强化Singleton属性

这里主要写一下Java的Sigleton模式吧,下文讨论的内容引入了其他博文
http://blog.csdn.net/cnyyx/article/details/7482735
http://my.oschina.net/alexgaoyh/blog/261106?fromerr=FT8qyEHA

方法1、静态成员直接初始化

public class Singleton {    //私有化构造器,防止外部new Singleton()    private Singleton(){}    private static final Singleton instance = new Singleton();    public static Singleton getInstance(){        return instance;    }}

这种方法在类加载的时候就会创建一个Singleton对象,不管该资源是否被请求,占用Jvm内存。

方法2根据lazy initialization思想,使用到时才初始化

public class Singleton {    private Singleton(){}    private static Singleton instance;    public static synchronized Singleton getInstance(){        if(null == instance){  //@1            instance = new Singleton(); //@2        }        return instance;    }}

该方法加载了同步锁,可以防止多线程在执行getInstance方法得到2个对象,如果不加synchronized关键字,考虑线程A、B
A执行 @1 还未执行 @2
B执行 @1 还未执行 @2
将会得到2个对象
缺点:只有在第一次调用的时候才需要同步,一旦instance部位null了,系统依旧花费同步锁开销,有点得不偿失

方法3:在2的基础上,改进标注:尽量减少锁资源

    private Singleton(){}    private static Singleton instance;    public static Singleton getInstance(){        if(null == instance){  //@1            synchronized (Singleton.class){ //@2                instance = new Singleton(); //@3            }        }        return instance;    }

这种写法减少了锁开销,但是在如下情况,却创建了2个对象:
a:线程1执行到1挂起,线程1认为singleton为null
b:线程2执行到1挂起,线程2认为singleton为null
c:线程1被唤醒执行synchronized块代码,走完创建了一个对象
d:线程2被唤醒执行synchronized块代码,走完创建了另一个对象
所以看出这种写法,并不完美。

方法4:为了解决上述3的问题,引入双重检查锁定

    private Singleton(){}    private static Singleton instance;    public static Singleton getInstance(){        if(null == instance){  //@1            synchronized (Singleton.class){//@2                if(null == instance){//@3                    instance = new Singleton();//@4                }            }        }        return instance;    }

在同步锁代码块内部,再判断一次对象是否为null,为null才创建对象。这种写法已经接近完美:
a:线程1执行到1,已经进入synchronized的时候,线程挂起,线程1占有Singleton.class资源锁;
b:线程2执行到1,当它准备synchronized块时,因为Singleton.class被占用,线程2阻塞;
c:线程1被唤醒,判断出对象为null,执行完创建一个对象
d:线程2被唤醒,判断出对象不为null,不执行创建语句
如此分析,发现似乎没问题。
但是实际上并不能保证它在单处理器或多处理器上正确运行;
问题就出现在singleton = new Singleton()这一行代码。它可以简单的分成如下三个步骤:
mem= singleton();//1
instance = mem;//2
ctorSingleton(instance);//3
这行代码先在内存开辟空间,赋给singleton的引用,然后执行new 初始化数据,但是注意初始化是要消耗时间。如果此时线程3在执行步骤1的时候,发现singleton 为非null,就直接返回,那么线程3返回的其实是一个没构造完成的对象。
我们期望1,2,3 按照反序执行,但是实际jvm内存模型,并没有明确的有序指定。
这归咎于java的平台的内存模型允许“无序写入”。

方法5:在4的基础上引入volatile

    private Singleton(){}    private static volatile Singleton instance;    public static Singleton getInstance(){        if(null == instance){  //@1            synchronized (Singleton.class){//@2                if(null == instance){//@3                    instance = new Singleton();//@4                }            }        }        return instance;    }

Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。
但是5的写法,虽然理论上似乎可以解决无序写入问题。实际上并非如此。

方法6: 为了实现慢加载,并且不希望每次调用getInstance时都必须互斥执行,最好并且最方便的解决办法如下:(通过内部类实现多线程环境中的单例模式)

        private Singleton(){}    private static class InstanceHolder{        private static final Singleton instance = new Singleton();    }    public static Singleton getInstance(){        return InstanceHolder.instance;    }

JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,
并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题(方法4)。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低效问题(方法2)。
最后instance是在第一次加载InstanceHolder类时被创建的,而InstanceHolder类则在调用getInstance方法的时候才会被加载,因此也实现了惰性加载。

方法7:另一种写法是采用枚举(因为枚举类型的构造函数天生是私有的, 而且外部也不能new一个枚举值)

public enum A{        INSTANCE;        public void invoke(){...}}

3、通过私有构造器强化不可实例化的能力

有时候可能需要编写只包含静态方法和静态域的类,如工具类。最好给其提供一个私有构造器,否则编译器会为其生成一个默认的无参构造函数。

public class MobileUtils {    private MobileUtils(){}    /**     * 隐藏手机号中间4位     * @param mobile     * @return String 隐藏手机号中间4位之后的手机号     */    public static String hideMobile(String mobile) {        if (StringUtils.isEmpty(mobile)) {            return StringUtils.EMPTY;        }        if (mobile.length() < 8) {            return StringUtils.EMPTY;        }        return mobile.substring(0, 3) + "****" + mobile.substring(7);    }}

4、避免创建不必要的对象

 对于同时提供了构造器和静态工厂方法,通常使用静态工程方法而不是构造器,以避免 创建不必要的对象,比如Boolean.valueOf(String)比new Boolean(String)要好,构造器每次 被调用时都要创建一个新的对象,静态工厂方法则没有这种要求,也不会这样做 下面是Boolean的部分源码,查看其valueOf方法
     public static final Boolean TRUE = new Boolean(true);     public static final Boolean FALSE = new Boolean(false);     public static Boolean valueOf(String s) {        return toBoolean(s) ? TRUE : FALSE;     }     private static boolean toBoolean(String name) {        return ((name != null) && name.equalsIgnoreCase("true"));     }

5、消除过期对象

这里只能说学习,主要看的是ArrayList吧,对不用的对象是如何处理的。

protected void removeRange(int fromIndex, int toIndex) {         modCount++;         int numMoved = size - toIndex;         System.arraycopy(elementData, toIndex, elementData, fromIndex,         numMoved);         // clear to let GC do its work 就是这里!         int newSize = size - (toIndex-fromIndex);         for (int i = newSize; i < size; i++) {         elementData[i] = null;         }         size = newSize;     }     public static native void arraycopy(Object src,  int  srcPos,        Object dest, int destPos,int length);

6、覆盖equal时请遵守通用约定

简单总结一下:
1、是否是同一个对象的引用
2、类型转换
3、域的比较,在比较时,优先比较区分度大的域
注意:这里有个坑:equals方法的参数是Object,而不是自己定义的类。否则直接变成代码重载,而不是重写了!!
@Override的作用也可以看到了 书中36条:坚持使用Override注解

@Overridepublic boolean equals(Object anObject) {        if (this == anObject) {//是否是同一个对象的引用            return true;        }        if (anObject instanceof String) {            String anotherString = (String) anObject;//转换为该对象类型            int n = value.length;  //管家field的值比较,先比较区分度比较大的!            if (n == anotherString.value.length) {                char v1[] = value;                char v2[] = anotherString.value;                int i = 0;                while (n-- != 0) {                    if (v1[i] != v2[i])                            return false;                    i++;                }                return true;            }        }        return false;    }

7、clone方法

主要考虑Java 深拷贝和浅拷贝

浅拷贝(浅复制、浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。深拷贝(深复制、深克隆):被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。clone基本类型 深拷贝clone对象类型 浅拷贝,重写clone方法,将对象引入一一clone,即可实现深拷贝    // 改为深复制:    Student2 student = (Student2) super.clone();    // 本来是浅复制,现在将Teacher对象复制一份并重新set进来    student.setTeacher((Teacher) student.getTeacher().clone());serialization序列化 深拷贝

8、Comparable的理解

类的比较,主要有两种方法,一种是类实现Comparable接口,重写compareTo方法;另外一种是在集合比较的时候,新建一个Comparator,重写compare方法。与书中hashCode讲解有点关系的部分是:hashCode影响:HashSet、HashMap、HashTable
compareTo影响:TreeSet、TreeMap、Collections、Arrays。

自我感觉关于整数比较时,尽量直接<>直接比较,通过减法有可能溢出!
另外,有点不太相关的内容,在比较时,返回-1、0、1表示大小关系,其实只要返回一个符号(正负)就行,看到了Long的signum方法

public static int signum(long i) {        return (int) ((i >> 63) | (-i >>> 63));    }

直接通过移位,不需要比较操作符,一句话搞定!吊吊的!Long的rotate和reverse也很吊!

0 0
原创粉丝点击