《Effective Java中文版第二版》讀書筆記

說明

這裏是閱讀《Effective Java中文版第二版》的讀書筆記,這裏會記錄一些我的感受稍微有些重要的內容,方便之後查閱,可能會由於我的實力緣由致使理解有誤,如有發現歡迎指出。一些我的還不理解的會用斜線標註。java

第一章是引言,因此跳過。設計模式

第二章 建立和銷燬對象

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

含義

靜態工廠方法是指一個返回類的實例的靜態方法,例如:數組

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean,FALSE;
}

優勢

相對於一個類的構造器,靜態工廠方法的名稱沒有限制。

衆所周知,構造器的方法名是必須和類名同樣的,所以對於有多個參數類型相同的構造方法,一種方法是更改參數的順序,另外一種是增長一個flag來判斷執行哪一個構造方法。可是這樣對於使用者是不友好的,他必須熟悉API或者查閱開發文檔。假若使用靜態工廠方法,那麼能夠經過方法名來給予使用者良好的提示與說明。框架

不用再每次調用的時候建立一個新的對象。

這句話的典型應用是在設計模式的單例模式中,靜態工廠方法可以爲重複的調用返回相同的對象。函數

靜態工廠方法能夠返回原返回類型的任何子類型的對象。

構造方法是不能使用return語句的,它在使用時也只能產生自身這個類的一個對象,而靜態工廠方法可使用return語句,所以在選擇返回對象時就有了更大的靈活性。這個優點的應用不少,好比服務提供者框架模式工具

小結

應當熟悉靜態工廠方法和構造器的各自的長處,在合適的場景使用合適的方法。性能

第2條:遇到多個構造器參數時要考慮用構建器

在面對一個擁有多個屬性的類且構造方法擁有多個可選參數時,一個常見的方法是使用重疊構造器模式(建立多個構造方法,每一個構造方法比前一個構造方法有新的參數)。例如,第一個構造方法有兩個必須參數,第二個構造方法有兩個必須參數和一個可選參數,第三個構造方法有兩個必須參數和兩個可選參數,以此類推。可是當有許多參數的時候,代碼會變得很難編寫,也很難閱讀,甚至會容易出錯。測試

另外一個方法是使用javabean模式。由於構造過程被分到了多個調用中(爲每一個屬性的賦值調用該屬性的set方法),在構造過程當中,javabean可能處於不一致的狀態,這種問題難以發現。ui

第三種方法就是構建器模式(Builder模式)的一種形式。this

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 {
        // 必須屬性
        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 setCalories(int calories) {
            this.calories = calories;
            return this;
        }

        public Builder setFat(int fat) {
            this.fat = fat;
            return this;
        }

        public Builder setSodium(int sodium) {
            this.sodium = sodium;
            return this;
        }

        public Builder setCarbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
            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 n = new NutritionFacts.Builder(200,10).setCalories(20).setFat(30).build();

Builder模式十分靈活,能夠利用一個builder來建立多個相同的對象,而且對必須參數和可變參數的實現符合人類的正常思惟。另外,對於使用者而言,使用時的代碼更容易閱讀和編寫。

這種方法我在google的protobuf的java實現中見到過。

第3條:用私有構造器或者枚舉類型強化Singleton屬性

私有構造方法就不提了,這裏記錄一下第二個:

public enum A {
    INSTANCE;

    public void leaveTheBuilding() {...}
}

第4條:經過私有構造器強化不可實例化的能力

對於一些只包含靜態方法或者靜態屬性的類(好比工具類),咱們不但願他們被實例化。衆所周知,在缺乏顯式構造方法的時候,編譯器會默認添加一個無參的構造方法。若是爲了嚴謹,咱們能夠添加一個私有的構造方法,更能夠在這個構造方法中throw異常來停止程序。

第5條:避免建立沒必要要的對象

通常來講,最好能重用對象而不是在每次須要的時候就建立一個相同功能的新對象。

除了重用不可變的對象以外,也能夠重用那些已知不會被修改的可變對象。

能使用基本數據類型,就儘可能不要用對應的封裝類。

第6條:消除過時的對象引用

不能覺得有了垃圾回收機制後,就不須要考慮內存管理的事情了。

例如用數組來實現棧,當實現出棧操做,size-1後,棧頂座標後的元素對使用者來講就已是無效部分了,可是數組仍然擁有對它們的引用,所以垃圾回收機制不會將它們回收。解決辦法是在出棧時,將引用置空。

第7條:避免使用終結方法

除了特定狀況,不要使用終結方法(finalize)。

子類覆蓋了父類的終結方法後,子類的終結方法不會自動調用父類的終結方法,須要手動調用。

第三章 對於全部對象都通用的方法

第8條:覆蓋equals請遵照通用約定

約定的內容:

equals方法實現了等價關係。

  • 自反性:對於任何非null的引用值x,x.equals(x)都必須返回true。
  • 對稱性:對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。
  • 傳遞性:對於任何非null的引用值x、y和z,若是x.equals(y)返回true,而且y.equals(z)也返回true,那麼x.equals(z)也必須返回true。
  • 一致性:對於任何非null的引用值x和y,只要equals的比較操做在對象中所用的信息沒有被修改,屢次調用x.equals(y)就會一致地返回true,或者一致地返回false。
  • 非空性:對於任何非null的引用值x,x.equals(null)必須返回false。

實現高質量equals方法的訣竅:

  1. 使用==操做符檢查「參數是否爲這個對象的引用」。若是是,則返回true。
  2. 使用instanceof操做符檢查「參數是否爲正確的類型」。若是不是,則返回false。
  3. 把參數轉化爲正確的類型。由於轉換前進行過instanceof測試,因此確保會成功。
  4. 對於該類中的每一個「關鍵」字段,檢查參數中的字段是否與該對象中對應的字段相匹配。若是這些測試所有成功,則返回true;不然返回false。
  5. 當你編寫完成了equals方法以後,應該質問本身而且測試這三個問題:它是不是對稱的、傳遞的、一致的?固然,equals方法也必須知足自反性和非空性,不過一般都會自動知足。

一個簡單的列子:

public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof MyClass))
        return false;
    MyClass obj = (MyClass) o;
    return obj.field0 == this.field0 && obj.field1 == this.field1;
}

告誡:

  • 覆蓋equals時總要覆蓋hashCode。
  • 不要企圖讓equals方法過於智能。
  • 不要將equals聲明中的Object對象替換爲其餘的類型。
public boolean equals(MyClass o); // Don't do this!

第9條:覆蓋equals時總要覆蓋hashCode

若是沒有共同覆蓋equals方法和hashCode方法,那麼該類將沒法結合全部基於散列的集合一塊兒正常運做,這樣的集合包括HashMap、HashSet和HashTable。

約定:相等的對象必須具備相等的散列碼(HashCode)。

在散列碼的計算過程當中,必須排除equals比較計算中沒有用到的任何字段,能夠把冗餘字段(它的值能夠根據參與計算的其餘字段計算出來)排除在外。

不要試圖從散列碼計算中排除掉一個對象的關鍵部分來提升性能。

第10條:始終要覆蓋toString

提供好的toString實現可使類用起來更加溫馨。

第11條:謹慎地覆蓋clone

若是你繼承了一個實現了Cloneable接口的類,那麼你除了實現一個行爲良好的clone方法外,沒有別的選擇。不然,最好提供某些其餘的途徑來代替對象拷貝,或者乾脆不提供這樣的功能。

另外一個實現對象拷貝的好方法是提供一個拷貝構造方法或者拷貝工廠。

// 拷貝構造方法
public MyClass(MyClass mc);

// 拷貝工廠
public static MyClass newInstance(MyClass mc);

第12條:考慮實現Comparable接口

類實現了Comparable接口,就代表它的實例具備天然順序關係(natural ordering)。

約定:(符號sgn(表達式)表示數學中的signum函數,根據表達式的值爲負值、零和正值,分別返回-一、0和1)

  • 必須確保全部的x和y都知足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。(這也意味着,當且僅當y.compareTo(x)拋出異常時,x.compareTo(y)才必須拋出異常)
  • 必須確保這個比較關係是可傳遞的。x.compareTo(y) > 0 && y.compareTo(z) > 0成立意味着x.compareTo(z) > 0
  • 必須確保x.compareTo(y) == 0意味着全部的z都知足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
  • 強烈建議(x.compareTo(y) == 0) == (x.equals(y)),但這絕非必要。若違反了這個條件,應當給予說明。

比較浮點字段用Double.compare或者Float.compare。

若是一個類有多個關鍵字段,按照什麼樣的順序來比較是很是重要的。

compareTo方法中,若是兩個對應字段不相等,可使用該類的字段與傳入參數的字段的差值做爲返回值,但應確保差值是絕對正確的。

相關文章
相關標籤/搜索