1. 程式人生 > >Effective Java 第三版讀書筆記——條款6:避免建立不必要的物件

Effective Java 第三版讀書筆記——條款6:避免建立不必要的物件

通常來講,重用一個物件比建立一個功能相同的物件更加合適。重用速度更快,並且更接近現代的程式碼風格。如果物件是不可變的(條款 17),它總是可以被重用。

考慮一個極端的例子:

String s = new String("bikini"); // DON'T DO THIS!

這個語句每次執行時都會建立一個新的 String 例項,而這些例項的建立都是不必要的。如果這種用法發生在迴圈或者頻繁呼叫的方法中,就會建立數百萬個毫無必要的 String 例項。

改進後的版本如下:

String s = "bikini";

該版本使用單個 String 例項,而不是每次執行時建立一個新例項。

一些物件的建立可能會比其他物件的建立昂貴很多。 如果要重複使用這樣一個“昂貴的物件”,建議將其快取起來以便重用。 不幸的是,建立這樣一個物件並不總是很直觀的。 假設你想寫一個方法來確定一個字串是否是一個合法的羅馬數字。 下面是使用正則表示式完成此操作的最簡單方法:

// Performance can be greatly improved!
static boolean isRomanNumeral(String s) {
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"
); }

這個實現的問題在於它依賴於 String.matches 方法。 雖然 String.matches 是檢查字串是否與正則表示式匹配的最簡單方法,但它不適合在效能臨界的情況下重複使用。 因為它在內部為正則表示式建立一個 Pattern 例項,並且只使用一次,之後這個 Pattern 例項就會被 JVM 進行垃圾回收。 建立 Pattern 例項是昂貴的,因為它需要將正則表示式編譯成有限狀態機(finite state machine)。

為了提高效能,將正則表示式顯式編譯為一個 Pattern 例項(不可變)並且快取它,在 isRomanNumeral 方法的每個呼叫中重複使用相同的例項:

// Reusing expensive object for improved performance
public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile(
            "^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}

如果經常呼叫,改進之後的 isRomanNumeral 會使效能得到顯著提升。 而且將不可見的 Pattern 例項顯式建立允許我們給它起一個名字,這個名字通常比正則表示式本身更具可讀性。

另一種建立不必要的物件的方式是自動裝箱(autoboxing),它允許程式設計師混用基本型別和包裝的基本型別,根據需要自動裝箱和拆箱。 自動裝箱模糊不清,但不會消除基本型別和裝箱基本型別之間的區別。 考慮下面這個計算 int 範圍內正整數總和的方法。 要做到這一點,程式必須使用 long 型別,因為 int 型別不足以儲存最後的結果:

// 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;
}

這個程式的結果是正確的,但由於寫錯了一個字元,執行的結果要比實際慢很多。變數 sum 被宣告成了 Long 而不是 long ,這意味著程式構造了大約 2 31 2^{31} 個不必要的Long例項(每次往 Long 型別的 sum 變數中增加一個 long 型別的 i)。把 sum 變數的型別由 Long 改為 long 會使效能得到很大提升。這個教訓很明顯:優先使用基本型別而不是包裝的基本型別,也要注意無意識的自動裝箱

這個條目不應該被誤解為暗示物件建立是昂貴的,應該避免建立物件。 相反,建立和回收小的物件非常廉價,構造器只會做很少的工作,尤其在現代 JVM 實現上。 建立額外的物件以增強程式的清晰性,簡單性或功能性通常是件好事。