Item5 Avoid creating unnecessary objects
避免創建不必要的對象
一般情況下,最好重用功能上對等的對象,而不是創建新的對象。重用對象可以提高性能,並且樣式良好。如果對象是immutable的(private fields, no setter methods),那麼重用起來就比較方便。
不良例子:
String name = new String(“HelloWorld”);
每次執行時,都會創建一個新的String對象。其中構造方法的參數本身就是一個對象,這樣的構造方法沒有必要,如果這樣的語句出現在循環中,將會導致非常多的不必要的對象的創建。
修正:
String name = “HelloWorld”;
該語句創建了單一的對象。這個對象還可以被之後運行在同一個虛擬機總的其他使用相同字符串的代碼重用。
package com.googlecode.javatips4u.effectivejava.unnecessary;
public class UnnecessaryObjectsSample {
public static void main(String[] args) {
long nanoTime = System.nanoTime();
String name = null;
for (int i = 0; i < 10000; i++) {
// DO NOT DO THIS
name = new String("name");
}
System.out.println(System.nanoTime() - nanoTime);// 2262299
nanoTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
// CHANGE TO THIS
name = "name";
}
System.out.println(System.nanoTime() - nanoTime);// 867987
System.out.println(name);
}
}
可以使用工廠方法來避免創建不必要的對象。
package com.googlecode.javatips4u.effectivejava.unnecessary;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class BadMutableObjectUsage {
private final Date birthDate = null;
// DON'T DO THIS!
public boolean isBabyBoomer() {
// Unnecessary allocation of expensive object
// Here the two Date objects are immutable.
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0
&& birthDate.compareTo(boomEnd) < 0;
}
}
上面例子中兩個Date對象,即開始時間和結束時間是常量,所以不需要在isBabyBoomer方法中每次被調用時都進行創建,而應該作爲類的靜態變量進行static初始化。
package com.googlecode.javatips4u.effectivejava.unnecessary;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class BetterMutableObjectUsage {
private final Date birthDate = null;
private static final Date BOOM_START_DATE;
private static final Date BOOM_END_DATE;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START_DATE = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END_DATE = gmtCal.getTime();
}
// DON'T DO THIS!
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START_DATE) >= 0
&& birthDate.compareTo(BOOM_END_DATE) < 0;
}
}
同樣,對於適配器(Adapter)模式而言,由於Adapter是由後臺的對象支持的,所以對於同一個對象而言,沒有必要爲其創建多個適配器對象。例如Map接口的keySet方法返回Map的key set:
package com.googlecode.javatips4u.effectivejava.unnecessary;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class AdapterPattern {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("key", "value");
map.put("keys", "values");
// all the keySet are backed by the same Map object.
Set<String> keySet1 = map.keySet();
map.remove("keys");
Set<String> keySet2 = map.keySet();
System.out.println(keySet1 == keySet2);// true
System.out.println(keySet1.equals(keySet2));// true
}
}
對於同一個map對象,返回的Set是一樣的,改變其中的一個set,其他的set也隨之而變,因爲他們都是由一個後臺的Map對象來支持的。
另外,自動裝箱(auto-boxing)操作也會帶來隱式對象的創建:
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
// so many Long objects are created here.
// coz sum is Long but not long.
sum += i;
}
System.out.println(sum);
}
通過維持自定義的對象池(Object Pool)來避免創建不必要的對象是不好的,除非要創建的對象是重量級的對象。例如數據庫連接。一般來講,對象池會造成代碼複雜,同時性能下降,現代的JVM的GC已經高度優化。