首页 > 代码库 > Effective Java通俗理解(持续更新)

Effective Java通俗理解(持续更新)

  这篇博客是Java经典书籍《Effective Java(第二版)》的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约会持续1个月左右。

第1条:考虑用静态工厂方法代替构造器

  通常情况下我们会利用类的构造器对其进行实例化,这似乎毫无疑问。但“静态工厂方法”也需要引起我们的高度注意。
  什么是“静态工厂方法”?这不同于设计模式中的工厂方法,我们可以理解它为“在一个类中用一个静态方法来返回这个类的实例”,例如:

public static People getInstance() {    return new People();}

  它是一个“方法”,那么它不同于构造器,它可以随意修改方法名,这就带来第一个优点——有名称。有时一个类的构造器往往不止一个,而它们的名称都是相同的,不同的仅仅是参数,如果不同参数带来不同的含义这样对于调用方来说除了注释很难理解它们有什么不同的含义。例如BigInteger(int, int, Random)返回一个素数,但调用者很难理解API设计者所要想表达的意思,如果此时有BigInteger.probablePrime静态工厂方法,则能一目了然的清楚API设计者所要想表达的含义。举一个JDK的例子:Executors类,在这个类中有newFixedThread、newSingleThreadExecutor、newCachedThreadPool等静态方法,因为它们有“名字”,所有就较为清晰的明白API的含义。

  《Effective Java》中所提到的静态工厂方法第二个优点在于不用重复创建一个对象,实际上也就是勤加载或者称为饿汉式的单例模式。例如:

public class Instance() {    private static Instance instance = new Instance();    private Instance(){}    public static Instance getInstance() {    return instance;    }}

  静态工厂方法的第三个优点,可以返回原返回类型的任何子类型的。这句话初看不好理解,举个JDK中的例子:Collections类。 

List list = Collections.synchronizedList(new ArrayList()) 

  这个例子就说明了可以返回原返回类型的任何子类型的对象。
  关于静态工厂方法的第四个优点,在创建参数化类型实例的时候,它们使代码变得更加简洁,书中举了一个例子:

Map<String, List<String>> m = new HashMap<String, List<String>>();    //这会显得很繁琐

  给集合类提供静态工厂方法后:

public static <K, V> HashMap<K, V> newInstance() {    return new HashMap<K, V>();}
技术分享

   但是实际上从JDK7(包括JDK7)之后的集合类可以用以下简洁的代码代替:

Map<String, List<String>> m = new HashMap<>();

  静态工厂方法也有两个缺点:一是公有的静态方法所返回的非公有类不能被实例化,也就是说Collections.synchronizedList返回的SynchronizedList不能被实例化;二是查找API比较麻烦,它们不像普通的类有构造器在API中标识出来,而是和其他普通静态方法一样,鉴于此,书中提到了几种惯用名称:

  valueOf
  of  
  getInstance
  newInstance
  getType
  newType

第2条:遇到多个构造器参数时要考虑用构建器

  你是否写过下面类似的代码:

public void Student()  {    /*必填*/    private String name;    private int age;    /*选填*/    private String sex;    private String grade;    public Student(String name, String sex) {        this(name, sex, 0);    }    public Student(String name, String sex, int age) {    this(name, sex, age, “”);    }    public Student(String name, String sex, int age, String grade) {    this.name = name;    this.sex = sex;    this.age = age;    this.grade = grade;    }}

  当我想实例化一个名字叫“kevin”,性别男,但是不写年龄,只有年级“1年级”,这个时候代码就:不得不要为年龄这个参数传递值。如果新增一个只含年级的构造方法,那又将多出许多代码,更严重的是,如果没有一份详尽的文档或者注释,看到如此多的构造方法将无从下手,这就是非常常见的重叠构造器。

Student student = new Student(“Kevin”, “男”, “0”, “1年级”);

  当然还有另外一种方法,只有一个必填项的构造方法,而其他选填项利用setter方法传递。例如:

Student student = new Student(“kevin”, “男”);student.setGrade(“1年级”);

  这实际上导致了在构造过程中JavaBean可能处于不一致的状态,也就是说实例化对象本该是一气呵成,但现在却分割成了两大步,这会导致它线程不安全,进一步引发不可预知的后果。
  书中提到较为“完美”的解决方案就是利用“Builder模式(建造者模式)”,有关此设计模式可以查看《建造者模式》。这种解决方案属建造者模式的一种形式,其核心就是不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象,再调用类似setter的方法设置相关可选参数。构建器模式如下所示:

/** * 构建器模式 * Created by yulinfeng on 2017/8/3. */public class Student {    /*必填*/    private String name;    private int age;    /*选填*/    private String sex;    private String grade;    public static class Builder {        private String name;        private int age;        private String sex = "";        private String grade = "";        public Builder(String name, int age) {            this.name = name;            this.age = age;        }        public Builder sex(String sex) {            this.sex = sex;            return this;        }        public Builder grade(String grade) {            this.grade = grade;            return this;        }        public Student build() {            return new Student(this);        }    }    private Student(Builder builder) {        this.name = builder.name;        this.age = builder.age;        this.sex = builder.sex;        this.grade = builder.grade;    }}

  客户端代码:

Student student = new Student.Builder("kevin", 23).grade("1年级").build();

  这样的客户端代码很容易边写,并且便于阅读。对于不了解的可能来说利用构建器模式编写Student类不算易事,甚至代码比重叠构造器的代码更多。所以当可选参数在很多时,谨慎使用重叠构造器,而是使用构建器模式。

——170803

Effective Java通俗理解(持续更新)