1. 程式人生 > >《Effective Java 2nd》第2章 創建和銷毀對象

《Effective Java 2nd》第2章 創建和銷毀對象

weak 避免 let nes 枚舉類型 one class eas sts

目錄 第1條:考慮使用靜態工廠方法代替構造器 第2條:遇到多個構造器參數時考慮用構建器 第3條:用私有構造器或者枚舉類型強化Singleton屬性 第4條:通過私有構造器強化不可實例化的能力 第5條:避免創建不必要的對象 第6條:消除過期的對象引用 第7條:避免使用終結方法

第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 final
int 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章 創建和銷毀對象