EffectiveJava 读书笔记1 创建和销毁对象
第1条:考虑用静态工厂方法代替构造器
?
静态工厂方法有名称,可以更好的描述返回的对象特性。比如同一个类的两个构造方法,方法参数类型相同,但顺序不同,用户根本无法从字面上区分两个构造器的区别静态工厂方法可以控制对象的创建(是否单例,是否缓存,是否禁止创建)可以返回原类型的任何子类型对象
在面向接口的编程中,因为接口不能有静态方法,按惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化类中。(Collection,Collections)。客户端通过接口来引用被返回的对象而不是直接用它的实现类来返回,是一种良好的编程习惯。
这样做的另一原因是,可以在现有接口框架下,通过不同的实现类,来完成框架的扩展。JDBC在创建参数化类型实例的时候,它使代码更加简洁。(方法声明的冗长。。。)
缺点:类如果不含有公有的或者受保护的构造器,就不能被子类化静态工厂方法与其他静态方法实际没有任何区别,在JAVADOC中,很难识别。
常用静态工厂方法名称:valueOf, of, getInstance, newInstance, getType, newType
第2条:遇到多个构造器参数时要考虑用构建器无论是静态工厂方法还是构造器方法,在遇到多个构造器参数时都会显得很繁杂。一般程序员使用重叠构造器模式(telescoping constructor),设置多个构造器,包括无参数,1个参数,两个参数。。直到覆盖全部参数。很明显,这种方式在构造器参数很多时,是不可行的。并且,在参数稍多的时,使用者必须要一个一个数着参数来分别确定它们代表什么意思。
另一种方法是java bean方式,即先用无参构造器创建一个对象,然后通过setter向需要赋值的字段赋值。由于构造过程分到了几个不同阶段,JAVA BEAN可能出于不一致状态,并且组织了做成不可变的可能,有线程安全的问题。
最合适的解决访华四是Builder模式:
?
public class NutritionFacts {private final int servingSize;private final int servings;private final int calories;private final int fat;private final int sodium;private final int carbohydrate;public static class Builder{//Required parametersprivate final int servingSize;private final int servings;//Optional parameters - initialized to default valuesprivate int calories = 0;private int fat= 0;private int carbohydrate = 0;private int sodium= 0;public Builder(int servingSize, int servings){this.servingSize = servingSize;this.servings = servings;}public Builder calories(int val){calories = val;return this;}public Builder fat(int val){fat = val;return this;}public Builder carbohydrate(int val){carbohydrate = val;return this;}public Builder sodium(int val){sodium = val;return this;}public NutritionFacts build(){return new NutritionFacts(this);}}private NutritionFacts(Builder builder){servingSize = builder.servingSize;servings= builder.servings;calories= builder.calories;fat= builder.fat;sodium= builder.sodium;carbohydrate= builder.carbohydrate;}}
?
?
NutritionFacts cocaCola = new NutritionFacts.Builder(240,0).calories(100).sodium(35).carbohydrate(27).build();
?
还可以使用单个builder构建多个对象,做成抽象工厂。1.5以后还可以使用泛型。
?
可以看出,Builder模式比重叠构造器可能更加冗长,所以只有在参数足够多的时候才使用。
?
第3条:用私有构造器或者枚举类型强化Singleton属性
一般来说,实现singleTon模式,是将构造方法用private修饰,并将要singleton的类设置一个public static final引用指向一个唯一的实例对象。但这种方式可能被反射破解,改进的方式是在私有构造器中设置一个判断,如果要生成第二个实例,则抛出异常。
?
另一种方法,是使用静态工厂方法,将真正的唯一实例保存在一个private static final引用中,和前面的方法相比,虽然前面的方法是用了公有域方法,可以实现内联提高效率,但是现代JVM都对public static方法做了调用内联,因此效率没有损失。另外,工厂方法提高了灵活性,为进一步修改(去掉singleton,控制实例)提供了修改空间。
?
从1.5起,第三种方法出现了:
?
public enum Elvis{
INSTANCE;
}
?
这种方式功能上与公有域方法相近,但更简洁,且提供了序列化机制,可以抵御反射攻击,是实现Singleton的最佳方法。
?
第4条:通过私有构造器强化不可实例化的能力
对于某些仅提供静态工具方法的工具类来说,实例化是没有意义的,这时应该显式的提供私有构造器杜绝将其实例化的行为。甚至在私有构造器中抛出throw new AssertionError()异常,这个写法有点误导,所以需要加入注释说明。
?
这种用法也有副作用,及被修饰的类是无法被继承的(子类化)
?
第5条:避免创建不必要的对象
静态工厂优先于构造器,因为静态工厂可能会重用对象,而构造器总是构造新的对象
?
例如对于日期比较工具,获得Calendar的实例不要写在实例方法中,而应该写在静态代码区中,这样仅生成一次可以反复重用。这样的效率差距可能达上百倍或更多。
?
优先使用基本类型而不是包装类
?
第6条:消除过期的对象引用
如果用数组作为栈容器,push对象超过数组上限就生成一个新的更大的数组。先进栈再弹栈的元素将不会被回收(在数组没有被重新分配的情况下,数组还是保留着这些元素的引用),这被称为过期引用。
?
用手动置空的方式可以消灭过期引用,但不要过度使用,这回造成代码零乱的情况,不是一种好的代码风格。
?
消除过期引用最好的办法是让包含该引用的变量结束其生命周期。
?
只要类是自己管理内存,程序员就应该警惕内存泄漏问题。
?
另一个很可能发生内存泄漏的地方是缓存。可以用WeakHashMap代表缓存,当缓存中的项过期后,它们就会被自动删除。只有当所要的缓存项的生命周期由该键的外部引用而不是由值决定时,WeakHashMap才能用。
?
由于缓存的数据有价值与否是一个见仁见智的问题,可以将清楚工作交给一个后台线程(Timer/ScheduledThreadPoolExecutor)
?
第7条:避免使用finalize方法
finalize方法的缺点:
不能保证被及时执行。用finalize方法关闭已经打开的文件是严重错误的。
finalize方法的执行时间点在不同的JVM中表现大相径庭,所以使用它,对可移植性是有害的。
JAVA也不保证finalize方法一定会被执行
未被捕获的异常在finalize方法中抛出,会被忽略,且finalize方法会终止。
使用finalize方法有严重的性能问题
?
替代方式是,提供一个显式的终止方法,要求该类的客户端在每个实例不再使用时调用。比如InputStream的close方法。最好结合finally方法使用
?
?
?