Effective Java-第二章
第1章
如何最有效地使用Java程序设计语言机器基本类库:java.lang,java.util,java.util.concurrent和java.io。
Sun JDK1.6_05版本
第2章 创建和销毁对象
创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。
1. 考虑用静态工厂方法代替构造器
为了让客户端获取对象的一个实例,最常用的方法就是提供一个公共的构造器,还有另一种方法:类可以提供一个公共的静态工厂方法(static factory method),它只是一个返回类的实例的静态方法。
注意静态工厂方法与设计模式中的工程方法模式不同。
优点
- 静态工厂方法与构造器不同的第一大优势在于,它们又名称:如果构造器的参数本身没有确切的描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户端代码也更易于阅读。例如:BigInteger(int, int, Random)返回的BigInteger可能为素数,如果用名为BigInteger.probablePrime的静态工厂方法来表示,显得更为清楚。
- 静态工厂方法与构造器不同的第二大优势在于,不必再每次调用它们的时候都创建一个新对象:不可变类可以使用预先创建好的实例,或则将创建好的实例缓存。
- 静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象:选择返回对象的类时就有更大的灵活性。例如Java Collections Framework的集合接口有32个便利实现,分别提供了不可修改的集合,同步集合等。几乎所有这些实现都通过静态工厂方法在不可实例化的类java.util.Collections中导出,所有返回对象的类都是非公有的。
- 静态工厂方法的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。
缺点
- 类如果不含公有的或受保护的构造器,就不能被子类化。
- 它们与其他的静态方法实际上没有任何区别。静态工厂方法的一些惯用名称:valueOf,of,getInstance,newInstance,getType,newType
总结:静态工厂更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。
2. 遇到多个构造器参数时要考虑用构造器
静态工厂和构造器有个共同的局限性:他们都不能很好地扩展到大量的可选参数。
面对很多参数的类,如何创建类?
(1)重载构造函数
package org.github.effective.p2; 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 NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbonhydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbonhydrate; } }
重载构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难阅读。
(2)JavaBeans模式
调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数。
缺点:
因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。
JavaBean模式阻止了把类做成不可变的可能。
(3)Builder模式
不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造前期得到一个builder对象,然后客户端在Builder对象上调用类似于setter的方法,来设置每个相关的可选参数,最后客户端调用无参的build方法来生成不可变的对象。这个Builder是它构建的类的静态成员类。
package org.github.effective.p2; public class NutritionFacts2 { 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 { private final int servingSize; private final int servings; private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { this.calories = val; return this; } public Builder fat(int val) { this.fat = val; return this; } public Builder carbohydrate(int val) { this.carbohydrate = val; return this; } public Builder sodium(int val) { this.sodium = val; return this; } public NutritionFacts2 build() { return new NutritionFacts2(this); } } private NutritionFacts2(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.calories = builder.calories; this.fat = builder.fat; this.sodium = builder.sodium; this.carbohydrate = builder.carbohydrate; } public static void main(String[] args) { NutritionFacts2 cocaCola = new NutritionFacts2.Builder(240, 8).calories(100).sodium(50).carbohydrate(27).build(); } }
总结:如果累的构造器或静态工厂中具有多个参数,这几这种类时,Builder模式就是不错的选择,特别是当大多数参数都是可选的时候。
3. 用私有构造器或枚举类型强化Singleton属性
Singleton指仅仅被实例化一次的类,通常被用来代表那些本质上唯一的系统组件。
创建单例的几种方法:
(1)
public class Elvis { public static final Elvis INSTANCE = new Elvis() private Elvis(){} public void m(){......} }
但是享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。
(2)
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis(){} public static Elvis getInstance(){ return INSTANCE; } public void m() {......} }
为了利用其中一种方法实现的Singleton类变成是可序列号的。仅仅在声明上加上implements Serializble是不够的,为了维护并保证Singleton,必须声明所有实例域都是transient的,并提供一个readResolve方法。否则每次反序列化一个序列化的实例时,都会创建一个新的实例。
(3)包含单个元素的枚举类型
public enum Elvis { INSTANCE; public void m(){.......} }
这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,防止多次实例化,即使是在面对复杂的序列化或反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
4. 通过私有构造器强化不可实例化的能力
工具类不希望别实例化,实例对它没有任何意义。
public class Utility { private Utility() { throw new AssertionError() } }
由于显式的构造器是私有的,所有不可以在该类的外部访问它,AssertionError不是必须的,但是它可以避免不小心在类的内部调用构造器。保证该类在任何情况下都不会被实例化。
5. 避免创建不必要的对象
一般来说,最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。如果对象是不可变的(immutable),就始终可以被重用。
6. 消除过期的对象引用
清空对象引用应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。
7. 避免使用终结方法
finalizer方法通常是不可预测的,也是很危险的,一般情况下是不必要的。
x.参考文档
《Effective Java中文版 第2版》