高效Java05:避免建立不必要的物件
阿新 • • 發佈:2019-02-19
就像我們大部分人所知道的,最好能重用物件,而不是每次都重複建立一個功能相同的新物件,下面舉幾個例子說明這個點。
重用不可變物件
如果物件是不可變的,那麼它就始終可以被重用。對於同時提供了靜態工廠方法和構造方法的不可變類,通常使用靜態工廠方法而不是構造方法,以避免建立不必要的物件,儘管同時提供兩種方法的場景不太多。例如靜態工廠方法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物件。從這裡,可以看到結論很明顯,要優先使用基本型別二不是裝箱基本型別,要注意這種自動裝箱的問題。