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


說明

這里是閱讀《Effective Java中文版第二版》的讀書筆記,這里會記錄一些個人感覺稍微有些重要的內容,方便以后查閱,可能會因為個人實力原因導致理解有誤,若有發現歡迎指出。一些個人還不理解的會用斜線標注。

第一章是引言,所以跳過。

第二章 創建和銷毀對象

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

含義

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

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

優點

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

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

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

這句話的典型應用是在設計模式的單例模式中,靜態工廠方法能夠為重復的調用返回相同的對象。

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

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

小結

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

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

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

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

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

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函數,根據表達式的值為負值、零和正值,分別返回-1、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方法中,如果兩個對應字段不相等,可以使用該類的字段與傳入參數的字段的差值作為返回值,但應確保差值是絕對正確的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM