整理Effective Java書中Item 6: Avoid creating unnecessary objects心得筆記

主旨

如果是不可變的物件(Immutable object),重複使用該物件可以讓效能以及資源控制更好,例如之前提到的靜態工廠方法Boolean.valueOf(String)

點出問題

  1. 底層封裝了處理方式

問題在於很多開發者不知道其實自己已經不自覺創了很多物件,尤其是java開發者在現在調用api便利的情況下,也沒有去探究底層原理,積少成多造成系統壓力。下面範例判斷字符串是否為有效的羅馬數字。

public class StringUtils {
    static boolean isNumeral(String s) {
        return s.matches("[0-9]+");
    }
}

看起來沒甚麼問題,使用上也正確,但卻有浪費資源問題,你也不知道自己有創建物件,而且是不斷的創建這個昂貴的對象。沿著matches原始碼往下追,最後可以找到new Pattern(regex, 0);每次使用一次創建一次隨即就gc。

public static Pattern compile(String regex) {
    return new Pattern(regex, 0);
}

為了解決問題,將regex Pattern編譯為 Pattern 實例(它是不可變的),作為class初始化的一部分,緩存它,並在每次調用 isRomanNumeral 方法時重複使用同一個實例,這樣就不用每次都創建Pattern。這裡效能大幅提升。

public class StringUtils {

    private static final Pattern NUMBER = Pattern.compile("[0-9]+");

    static boolean isNumeral(String s) {
        return NUMBER.matcher(s).matches();
    }
}

2.自動裝箱封裝了處理方式

Autoboxing blurs but does not erase the distinction between primitive and boxed primitive types.

static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

這裡也是一個非常不明顯的地方,每次執行sum += i;都會創建Long的實例。將Long sum = 0L;改為long sum = 0L;可以大幅提高性能,這個是一個很容易犯的錯誤。有興趣的可以跑看看下面範例感受一下性能差距。

static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    sum();
    long endTime = System.currentTimeMillis();
    System.out.println("That took " + (endTime - startTime) + " milliseconds");
}

小結

讀完這小節可以回去看看自己過去開發的專案,馬上就可以找到不少類似的情況,慢慢修正它吧。