码迷,mamicode.com
首页 > 编程语言 > 详细

Effective Java 读书笔记

时间:2016-06-12 03:15:47      阅读:265      评论:0      收藏:0      [点我收藏+]

标签:

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注解

@Override
public 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也很吊!

Effective Java 读书笔记

标签:

原文地址:http://blog.csdn.net/bupt09211303/article/details/51603747

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!