本章主要讨论创建和销毁对象的时候应该遵守的规则和注意的地方。 本系列是著名JAVA必读书籍之一的《Effective JAVA》的读书笔记,注意,本书不适合没有JAVA基础或者编程经验的人阅读。属于高级教程。

第一条 用静态工厂方法代替构造器

静态工厂方法大概长下面这样:

public static MyClass newInstance(int arg1, int arg2){    return new MyClass(arg1, arg2);}

优点

为什么要用上面的方法来获得对象的实例呢?用构造器不就好了吗? 理由有如下几点: 1. 静态工厂方法有名称!构造器必须和类同名,这意味着,两个或者两个以上的构造器存在的时候,你只能通过它们的参数来区分它们。而很多时候,调用者会不小心把参数的顺序搞混! 如果使用带有合适名称的静态工厂方法,一切就都迎刃而解咯。 2. 静态工厂方法可以用来返回缓存的对象实例。如果你调用了构造器,不管你愿意或者不愿意,构造器都将扔给你一个新对象。静态工厂方法可以编写地更加灵活,使得针对同样的入参,总是返回同一个对象。 可以参考Boolean.valueOf(boolean) 3. 可以返回原返回类型的任何子类型的对象,从而拥有更大灵活性。 举例: - 比如Collections类,该类可以返回一些不可变的集合对象,这些对象都以私有内部类的形式定义,而由于该类的构造器被私有化,因此任何人都不能去new一个Collections类,所以它为我们提供一系列静态工程方法的同时,防止任何人去修改它本身和它返回的东西。 Collections使用静态工厂方法的模式值得我们借鉴。当我们为某个接口提供预设的实现而又不想让这实现公有化时,可以使用这种模式来仅仅返回实例。 - 静态工厂方法除了可以返回私有的子对象以外,还可以根据情况,动态地返回子对象。比如EnumSet,竟然根据枚举类型的大小返回不同的结果,如果元素<=64个,就返回RegularEnumSet,超过了就使用JumboEnumSet的实例。 4. 静态工厂方法可以做类型推导,从而在创建参数化类型实例的时候,是代码更简洁,比如:

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

注:书中在举例的时候,JAVA才出到1.6版本,因此才会提及这个特性,实际上,类型推导在1.7版本JDK已经帮我们实现了,后面的new语句不需要再声明类型,只需要new HashMap<>()就可以了。 可以用静态工厂方法:

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

来这样做类型推导:

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

注:虽然1.6版本的JAVA集合框架都没有实现类型推导,但是谷歌著名的GUAVA工具框架用静态工厂方法帮我们实现了。 ### 缺点 任何设计都有其缺点,静态工厂方法也是有缺点的,缺点如下: 1. 静态工厂方法要想实现上面优点中的第3点,返回内部类或者子类,首先,该内部类或者子类需要有public或者protected类型的构造器才行,这就导致我们无法做到任何人只能通过我们提供的静态工厂方法来获得实例,他们也可以想办法使用其他办法获得。另一方面也意味着没有非私有化构造器的类是不能用静态工厂方法来返回的,也就无法像Collections那样方便地提供一系列方法返回实例了。 2. 静态工厂方法和其他静态方法没有任何区别,调用者只能依靠名字来区分它们。为了解决这个问题,我们必须遵守好的命名规范,尽可能地告诉调用者,我这是静态工厂方法,用我这个方法,是可以得到某某某的实例的: - valueof - of - getInstance - newInstance - getType - newType

第二条 遇到多个构造器参数时考虑使用构建器(Builder)

本条主要是讲遇到一个对象有几十个域(Field)时,构造器的入参可多达几十个,前面第一条的静态工厂方法也无法解决这个问题。 书中先给出了一系列我们平常最喜欢用的、拙劣的实现手段。 ### 层叠构造器 就是那种一堆构造器,每个构造器都比上一个多一个入参,最终有一个非常全面的构造器,包含所有的入参。代码如下:

class ClassForShowTelescopingConstructor {        private int field1;        private int field2;        private int field3;        private int field4;        public ClassForShowTelescopingConstructor(int field1) {            this(field1, 0);        }        public ClassForShowTelescopingConstructor(int field1, int field2) {            this(field1, field2, 0);        }        public ClassForShowTelescopingConstructor(int field1, int field2, int field3) {            this(field1, field2, field3, 0);        }        public ClassForShowTelescopingConstructor(int field1, int field2, int field3, int field4) {            this.field1 = field1;            this.field2 = field2;            this.field3 = field3;            this.field4 = field4;        }    }

如果你曾经为写出上面这样的代码而沾沾自喜,觉得很厉害的样子,那你就可以抽自己一嘴巴子了,作者已经抽过了T_T 这个模式有两个致命缺点: 1. 随着参数的增加,排列组合的可能性几何增长,你根本不可能提供所有的参数的排列的构造器,于是调用者不得不调用距离他想要的构造器最相近的那一个,然后把多余的参数都塞上0、null这种状态。即便真的巧合遇到他想要的,但是他还是不知道,你已经背地里帮他把其他的参数都塞了一堆0、null这样的数值进去了……这真的是一种好的做法吗? 2. 数量繁多的构造器在IDE的自动补全列表里会撑开一个长长的名字一模一样的名单,调用者不得不小心翼翼并猜测哪个才是他想要的,或者干脆点进去看一眼源代码,否则,他极有可能搞混两个类型相同参数的顺序,或者调用到了他根本不想要的那一个。而这一切编译器不会提前发现错误……

JavaBean模式

这是当今Web领域最流行的一种构造对象的方式。 不可否认,至少这种模式已经能够解决上面层叠构造器的麻烦,为我们提供一个清晰可读的代码:

class ClassForShowJavaBean {        private int field1;        private int field2;        private int field3;        private int field4;        public ClassForShowJavaBean() {        }        public void setField1(int field1) {            this.field1 = field1;        }        public void setField2(int field2) {            this.field2 = field2;        }        public void setField3(int field3) {            this.field3 = field3;        }        public void setField4(int field4) {            this.field4 = field4;        }    }class ClassMain{        public static void main(String[] args) {            ClassForShowJavaBean classForShowJavaBean = new ClassForShowJavaBean();            classForShowJavaBean.setField1(1);            classForShowJavaBean.setField2(2);            classForShowJavaBean.setField3(3);            classForShowJavaBean.setField4(4);        }    }

由于Spring框架和JSON的流行,这种模式现在在Web项目中很常见。但是书中还是指出了不足的地方: 1. 这种方法没法保证调用者在对象完全构造完毕的时候才使用对象,构造的过程是分离的,分很多步,而这可能导致对象处于一种不一致的状态,使用不一致状态的对象可能导致难以察觉的错误。(其实简单点儿说就是,这种模式没法一次就构造出可以使用的对象,需要分成多步,从而造成不一致性) 2. 这种方法导致无法轻易将类做成不可变的对象,因为它要求我们必须提供Setter方法,从而给予外人改变其域值的机会,这在多线程程序中会有问题。