《Effective Java 2nd》第2章 創建和銷毀對象
第2章 創建和銷毀對象
第1條:考慮使用靜態工廠方法代替構造器
通過使用靜態工廠方法而不是使用構造器來創建類。
舉例:Boolean.valueOf(boolean)方法,將boolean轉換為Boolean對象引用。
有以下優勢:
1)靜態工廠方法有名稱,可以表示方法的意思
2)不必在每次調用的時候都創建新對象。
不可變類可預先構建實例,緩存起來重復使用。
3)可以返回原返回類型的任何子類型的對象
4)在創建參數化類型的時候代碼更簡潔。
//我們平時創建list List<String> list = new Arraylist<>(); //使用google 工具類 List<String> list = Lists.newArrayList();靜態工廠方法常用名稱:
valueOf、of、getInstance、newInstance、getType、newType(如newArrayList)
第2條:遇到多個構造器參數時考慮用構建器
當有很多個構造參數,且有幾個參數是可選的,考慮使用Builder
public class NutritionFacts { /** 必填參數 */ private final int servingSize; private final int servings; /** 可選參數 */ private final int fat; private final int sodium; public static class Builder { private finalint servingSize; private final int servings; private int fat = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder fat(int fat) { this.fat = fat; return this; } public Builder sodium(int sodium) { this.sodium = sodium; return this; } public P build() { return new P(this); } } private NutritionFacts(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.fat = builder.fat; this.modium = builder.modium; } }
客戶端代碼
NutritionFacts p = new NutritionFacts.Builder(200, 8).fat(100).sodium(35).build();
更高級使用:
public interface Builder<T> { public T build(); }
public static class NutritionFacts.Builder implements Builder<P>
這樣可以將Builder<NutritionFacts>傳給方法,並結合抽象工廠創建NutritionFacts實例。
第3條:用私有構造器或者枚舉類型強化Singleton屬性
舉例1:使用私有構造器
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} //私有構造器 //other code }
為了保證Singleton類是可序列化的
1)聲明加上implents Serializable
2) 所有實例域都是transient
3)提供一個readResolve方法
private Object readResolve() { return INSTANCE; }
舉例2:使用枚舉
public enum Singleton { INSTANCE; }
第4條:通過私有構造器強化不可實例化的能力
主要用於在寫工具類的時候。
public class XXXUtils { ? private XXXXUtils() { } //other code }
第5條:避免創建不必要的對象
當你應該重用現有對象的時候,不要創建新的對象。
舉例1:
m方法會被頻繁調用時,會創建n多的String實例。
public String m() { String s = new String("str"); //other code }
改進後,m方法被頻繁調用,但是s會被復用。
public String m() { String s = "str"; }
舉例2:
求所有Integer的和,因聲明為Long sum,而不是long sum,程序將創建約2的31次方個Long實例。
public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; //這裏每次都會創建一個Long實例。要當心無意識的自動裝箱。 } System.out.println(sum); }
第6條:消除過期的對象引用
主要講了內存泄露問題。
舉例1:類自己管理內存,易導致內存泄露。
下面是簡單的棧實現,程序每次測試都會通過,但是有個隱藏問題——”內存泄露“。
棧先增長,再收縮,棧中彈出的對象不會被當做垃圾回收,即使使用棧的程序不再引用這些對象。
public class Stack { private Object[] elements; private int size; private static final int DEFAULT_INIT_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INIT_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; //解決內存泄露的方法 //elements[size] = null; 清空過期引用 return result; } }
為何會出現內部泄露?
Stack通過數組保存數組元素,相當於自己管理內存。只要是類自己管理內存,就應該警惕內存泄露問題。
舉例2:內存泄露的另一個常見來源是緩存
緩存內存泄露:把對象引用放到緩存中,容易被遺忘,不再有用之後仍留在緩存中。
情形1:只要在緩存外有對某個項的鍵的引用,該項就有意義。
解決方法:使用WeakHashMap。
(記住只有當緩存項生命周期由該鍵的外部引用而不是值決定時,WeakHashMap才有意義)
情形2:緩存項生命周期是否有意義,不是很容易確定
解決方法:後臺線程定期清理 or 緩存添加新條目時順便清理(LinkedHashMap.removeEldestEntry()方法)
情形3:更復雜的緩存
解決方法:使用java.lang.ref
舉例3:內存泄露的第三個常見來源是監聽器和其他回調
比如註冊回調,但是沒有顯式地取消回調。解決方法:保存它們的弱引用(weak ref),如只將它們保存為WeakHashMap的鍵。
第7條:避免使用終結方法
終結方法就是finalize()方法。
Java語言規範不保證終結方法會被及時地執行,更不保證一定會被執行。
System.gc()增加了終結方法被執行的機會,但不保證一定會被執行。
《Effective Java 2nd》第2章 創建和銷毀對象