1. 程式人生 > >高效Java05:避免建立不必要的物件

高效Java05:避免建立不必要的物件

就像我們大部分人所知道的,最好能重用物件,而不是每次都重複建立一個功能相同的新物件,下面舉幾個例子說明這個點。

重用不可變物件

如果物件是不可變的,那麼它就始終可以被重用。對於同時提供了靜態工廠方法和構造方法的不可變類,通常使用靜態工廠方法而不是構造方法,以避免建立不必要的物件,儘管同時提供兩種方法的場景不太多。例如靜態工廠方法Boolean.valueOf(String)幾乎總是優於構造方法Boolean(String),從原始碼註釋中我們也能看到相應的提示資訊。

    /**
     * Allocates a {@code Boolean} object representing the
     * {@code
value} argument. * * <p><b>Note: It is rarely appropriate to use this constructor. * Unless a <i>new</i> instance is required, the static factory * {@link #valueOf(boolean)} is generally a better choice. It is * likely to yield significantly better space and
time performance.</b> * * @param value the value of the {@code Boolean}. */ public Boolean(boolean value) { this.value = value; } /** * Returns a {@code Boolean} instance representing the specified * {@code boolean} value. If the specified {@code
boolean} value * is {@code true}, this method returns {@code Boolean.TRUE}; * if it is {@code false}, this method returns {@code Boolean.FALSE}. * If a new {@code Boolean} instance is not required, this method * should generally be used in preference to the constructor * {@link #Boolean(boolean)}, as this method is likely to yield * significantly better space and time performance. * * @param b a boolean value. * @return a {@code Boolean} instance representing {@code b}. * @since 1.4 */ public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }

重用已知不會修改的可變物件

先看一段示例程式碼:

public class Item5 {
    public long getFoundedTimeMillisFromNow() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(1978, Calendar.NOVEMBER, 10, 0, 0, 0);
        return System.currentTimeMillis() - calendar.getTime().getTime();
    }
}

上面的程式碼計算七天後的時間,getFoundedTimeMillis()都會獲取Calendar的一個例項,此處Calendar非單例,如果多次呼叫則產生了多個Calendar例項,在我電腦上測試呼叫1000萬次耗時3843ms,如果我們對它進行簡單修改:

public class Item5 {
    private static long birthTimeMillis;
    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(1978, Calendar.NOVEMBER, 10, 0, 0, 0);
        birthTimeMillis = calendar.getTime().getTime();
    }
    public long getFoundedTimeMillisFromNow() {
        return System.currentTimeMillis() - birthTimeMillis;
    }
}

單獨提出用於對比的基準時間birthTimeMillis,類載入時通過一個Calendar獲取它的值,之後它不會再變化,修改後測試呼叫1000萬次耗時414ms。

注意自動裝箱問題

自Java 1.5開始引入了自動裝箱(autobox),使得基本型別與裝箱基本型別之間的差別變的模糊起來,如果不注意他們在語意上的差別則可能產生效能上的差異。

Long sum = 0L;

long startTime = System.currentTimeMillis();

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

上面這段程式碼計算所有正整數的和,經測試,耗時7838ms。現在我們對其進行修改:

long sum = 0L;

long startTime = System.currentTimeMillis();

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

重新對其測試,耗時758ms,從測試結果上我們可以看到兩者間明顯的差距,這是因為第一段程式碼將sum定義成了Long型別,導致每累加一次便會例項化一個Long物件。從這裡,可以看到結論很明顯,要優先使用基本型別二不是裝箱基本型別,要注意這種自動裝箱的問題。