第1条:考虑用静态方法而不是构造器
静态方法相对于构造器的优势:
1.静态工厂方法有名字
2.静态工厂方法不必每次调用都创建一个新的对象
3.静态工厂方法能返回任意类型的子对象
4.静态工厂可以根据调用时传入的不同参数而返回不同类的对象
5.静态工厂在编写包含该方法的类时,返回对象的类不需要存在
对于 4,比如EnumSet.java中的allOf方法:
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
EnumSet<E> result = noneOf(elementType);
result.addAll();
return result;
}
可以根据传入的参数,返回对于的对象的类。而对于5,除了构造方法的大多数方法都能通过反射而返回本地不存在,而是在 API接口、jar包等中的lei
静态方法相对于构造方法劣势:
1.没有公有或者保护构造方法的类不能子类化
2.程序员难以找到他们
对于1,用静态方法获取对象的前提是,对象的构造方法的访问修饰符是private,而这种情况下的类无法被继承,除此之外,用final修饰的类也无法被继承
第2条:遇到多个构造器参数时,考虑用构建者
通过创建不同的构造方法来设置参数,当可设置的成员变量较多时(4个或4个以上),这样让代码比较难写(容易把参数弄错),同时,之后也比较难读。
通过JavaBean的getter和setter来设置参数,当可设置的成员变量较多时(4个或4个以上),这样将参数的设置放到了多个调用中,但这样在使用过程中可以随时改变参数,从而无法保持对象的一致性,线程安全维护起来也比较麻烦。
Builder模式就可以很好得解决这个问题,例子如下:
// Builder Pattern
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 parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
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){
calories = val; return this;
}
public Builder fat(int val){
fat = val; return this;
}
public Builder sodium(int val){
sodium = val; return this;
}
public Builder carbohydrate(int val){
carbohydrate = 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, 8).calories(100).sodium(35).carbohydrate(27).build();
Builder也经常用于返回协变类型,协变类型是指:子类方法返回的对象类型是对应超类方法返回的对象类型的子类。
举一个例子,Pizza为抽象类,NyPizza和Calzone继承自Pizza
Pizza.java
// Builder pattern for class hierarchies
public abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER,SAUSAGE
}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings =
EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}
}
NyPizza.java
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
Calzone.java
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false; // Default
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
public Calzone build() {
return new Calzone(this);
}
@Override
protected Builder self() {
return this;
}
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
两个子类的实例化:
NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();
另外,Builder模式比起前面两种创建对象的方法,它多创建了一个Builder对象,所以我们只在参数比较多的时候(4个或4个以上),以及知道后续参数会增多时,使用Builder模式。
第3条:使用私有构造器或者枚举类型来强化Singleton属性
为了保证Singleton,应当将所有的实例属性声明为瞬时的(transient),并提供一个readResolve方法(条目89)。否则,每次反序列化一个已被序列化的实例时,将产生一个新的实例
第4条:通过私有化构造器强化不可实例化的能力
有些工具类的方法都用static进行修饰,我们不希望这样的类进行实例化。
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
... // Remainder omitted
}
因为一般情况下构造方法都可以被调用,这种时候最好像上面的例子那样加上注释。
第5条:优先使用依赖注入而不是硬连接资源
将变量直接传入,而不是在直接在对象中根据不同的情况构造
第6条:避免创建不必要的对象
反例
String s = new String("bikini"); // DON'T DO THIS!
正确的写法
String s = "bikini";
另一种会创建不必要对象的方式是自动装箱,比如下面会创建2^{31}不必要的Long实例:
// Hideously slow! Can you spot the object creation?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
第7条:消除过时的对象引用
文中举的例子有:
1.栈中pop出来的对象不会被回收,要主动清理为null;
2.忘记清理的缓存,将缓存的对象设为weakreference或及时清理;
3.监听器忘记取消监听;
第8条:避免使用终结方法和清理方法
第9条:优先使用try-with-resources而不是try-finally
try-with-resources语句让我们更容易编写必须要关闭的资源的代码.
以往的try-finally,在finally中也会出现异常,这样如果try和finally同时出现异常,那么最后的只能看到finally中的。
这时,try-with-resources就不会隐藏错误,若要使用这个语句,一个资源必须实现AutoCloseable接口,而这个接口只有一个返回类型为void的close(void-returning)方法。
// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
try (
BufferedReader br = new BufferedReader(new FileReader(path))
) {
return br.readLine();
}
}
注:本文所有的建议与例子都来自《Effective Java》