1. 程式人生 > >《Effect Java》第二章"建立和銷燬物件”筆記

《Effect Java》第二章"建立和銷燬物件”筆記

第一條:考慮用靜態工廠方法代替構造器

首先要指明一個誤區:靜態工廠方法不是指的設計模式裡面的工廠方法,他是指以靜態方法的形式建立物件(工廠就是用來建立物件的),例如:

public static User createNormalUser(){
    return new User();
}

public static User createVIPUser(){
    return new User(100);
}

書中描述這樣做的優點有以下幾點:

1.他們有名稱。通過以上例子就能看出來,有無名稱的好處,建立普通使用者和建立VIP使用者的區別僅僅在於建立是是否傳入引數,顯然靜態工廠方法更容易使用,產生的客戶端程式碼也更易閱讀。

2.不必在每次呼叫它們的時候建立一個新物件。這條指的是單例模式的用法。

3.它們可以返回型別的任何子型別的物件。這句話不太好理解,最起碼我是這樣。剛開始我以為是向上轉型,可是轉念一想,向上轉型還需要靜態工廠方法?然後仔細一看是返回的子類物件。像Collections裡的方法:

public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
}

不管是UnmodifiableRandomAccessList類還是UnmodifiableList類,它們都是在Collections裡面的內部類。Collections裡面包含大量的這樣的內部類。書中也說明了這樣的好處,可以返回物件,同時又不會使物件的類變為共有,這樣使得實現類變得非常簡潔。大家可以自己去檢視Collections類去體會一下。

4.在建立引數化型別例項的時候,他們使得程式碼更簡單。書中例子:

//以前版本
Map<String, List<String>> m = new HashMap<String, List<String>>();
//改為靜態工廠後
public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}
//例項化
Map<String, List<String>> m = HashMap.newInstance();

其實這個優點對於現在的JDK版本來說也不叫優點了。

//1.7之後
Map<String, List<String>> m =new HashMap<>();

書中闡述的靜態工廠方法缺點有兩個。

1.類如果不含共有的或者受保護的構造器,就不能被例項化。例如Collections裡面的UnmodifiableRandomAccessList和UnmodifiableList就不能被例項化。

2.與其他靜態方法沒有任何區別。所以無法區別哪些是靜態工廠方法和其他靜態方法。書中鼓勵用命令規範來彌補這個缺點。以下是常用名稱:

 valueOf(),of(),getInstance(),newInstance(),getType(),newType()

 

第二條:遇到多個構造器引數時要考慮選用構建器

 大家肯定都遇到過這種情況:

public Human(int height, int age) {
        this.height= height;
        this.age= age;
    }

public Human(int age) {
        this.age= age;
}

public Human() {}

public static void main(String[] args) {
   Human h= new Human(170,17);
}

當建立一個物件時,對於某些可選的引數屬性,大多數人都是用過載構造器來實現,也就是書中所述重疊構造器。這樣做的缺點在於建立物件的時候,表達不清晰,容易出錯。例如new Human(170,17),如果你把170和17填返了,那麼程式還是會正常執行的。對於引數更多的物件,可能一點不小心就會造成一些微妙的錯誤,而且程式並不能給我們這些錯誤任何提示。

然後書中闡述了另外一種方式,javabean模式。即:

private int height;
private int age;

public void setHeight(int height) {
    this.height = height;
}

public void setAge(int age) {
    this.age = age;
}

public static void main(String[] args) {
    Human h= new Human();
    h.setHeight(170);
    h.setAge(17);
}

這樣的好處在於設定引數的時候有了名字,這樣就大大減輕了出錯的機率。閱讀起來也很容易。可是這也同樣有著嚴重的缺陷,因為構造被分到了幾個呼叫中,所以可能會出現執行緒安全的問題。例項化類應該是一氣呵成的。

因此書中闡述了第三個解決方案:構建器

public class Human {

    private int height;
    private int age;
    private String body;

    public Human(Builder builder) {
        this.age = builder.age;
        this.body = builder.body;
        this.height = builder.height;
    }

    static class Builder{
        private int height=0;
        private int age;
        private String body="";

        public Builder(int age) {
            this.age = age;
        }

        public Builder setHeight(int height) {
            this.height = height;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setBody(String body) {
            this.body = body;
            return this;
        }

        public Human build(){
            return new Human(this);
        }
    }

    public static void main(String[] args) {
        Human human = new Builder(17).setHeight(170).build();
    }
}

這樣優點在於既能保證重疊構造器的安全性,又能保證javaBean模式的可讀性。完美融合了前面兩種的優點。而且還能保證某些必要的引數,例如age屬性。構建器的缺點在於建立物件的時候,必須先建立Builder構建器,增加了建立物件的額外開銷。