Effective Java Note-2

创建与销毁对象

遇到多个构造器参数时考虑用构建器

当构造器有多个可选的参数的时候,程序员往往使用重叠构造器模式(telescoping constructor), 即:

public class NutritionFacts
{
    private final int servingSize; //required
    private fianl int serings; required
    private final int calories; //optional
    private final int fat;      //optional
    private final int sodium;   //optional

    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 = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
    }
}  

这种写法不易与程序员的理解与记忆,很容易将参数的顺序弄错,而编译器也不会发现这种错误,从而导致难以发现的bug。还有一种模式是Java Bean模式,即:

public class NutritionFacts
{
    private final int servingSize; //required
    private fianl int serings; required
    private final int calories; //optional
    private final int fat;      //optional
    private final int sodium;   //optional

    public NutritionFacts(){}

    //setter
    public void setServiceSize(int val){ servingSize = val; }
    public void setServings(int val){ servings = val; }
    public void setCalories(int val){ calories = val; }
    public void setFat(int val){ fat = val; }
    public void setSodium(int val){ sodium = val; }     
}  

这种模式虽然易于理解和使用,但是有一个缺点,就是在构造过程中Java Bean可能处于不一致的状态,而且也不能将其设计成不可变(imutable)的类。后面介绍的构建器(builder)模式既能够有第一种模式的安全性,又有第二种模式的可读性。它的例子如下:

public class NUtritionFacts
{
    private final int servingSize; //required
    private fianl int serings; required
    private final int calories; //optional
    private final int fat;      //optional
    private final int sodium;   //optional

    public static class Builder
    {
        private final int servingsize; //required
        private fianl int serings; //required
        private final int calories = 0; //optional
        private final int fat = 0;      //optional
        private final int sodium = 0;   //optional

        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 NutritionFacts build()
        {
            return new NutritionFacts(this);
        }
    }

    public NutritionFacts(Builder builder)
    {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
    }
} 

以上的例子有三点需要注意。
1. Builder是一个内嵌类,而且是静态的。静态的内嵌类和静态的成员变量不一样。静态的内嵌类表示该类和外围类的实例没有关联。如果内嵌类不是静态的,那么必须先有外围类的实例,才能有内嵌类的实例,内嵌类的实例中会有对外围类实例的引用。
2. 细心的读者可能发现,在NutritionFacts(Builder builder)这个构造器方法中,能够使用builder这个实例的私有域,这是因为Builder是NutritionFacts的内嵌类,Builder的私有域对于NutritionFacts内的方法是可见的。 3. Builder中的setter方法返回的是Builder对象,在设值后都会return this,这是为了能够链式调用,如:

NutritionFacts cala = new NutritionFacts.Builder(240 , 0).calories(100).sodium(35).build();

用私有构造器或者枚举强化Singleton属性

Singleton指一个类只有一个实例。实现单例有3种方式。
第一种方法的公有静态成员是个final域:

public class Elvis
{
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {...}  
    ...  
} 

第二种方法中公有的成有是个静态工厂方法:

public class Elvis  
{
    private static final Elivs INSTANCE = new Elvis();
    private Elsvis(){...}
    public static Elvis getInstance()
    {
        return INSTANCE;
    }
}

需要注意的是,以上两种方法,如果要将其变成是可序列化(serializable)的,为了保证其Singleton的属性,需要将所有实例域声明为transient,并且提供一个readResolve方法。 第三种方法是将其定义为枚举类, 枚举类本身就是Singlenton,并且无偿提供了序列化机制。

通过私有构造器强化不可实例化的能力

对于一些工具类,如java.lang.Math, java.util.Coolections, 把基本类型的值或者特定接口的对象上的静态方法组织起来,实例对他们没有意义,确保它们不会被实例化的方法是将构造器定义为私有的(private), 如:

public class UtilityClass 
{
    private UtilityClass();
    ...
}  

这种做法的副作用是该类是不能被继承的。