《Effective Java》第二章:创建和销毁对象

第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》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。