Effective Java學習筆記

建立和銷燬對象

第一條:考慮用靜態工廠方法替代構造器

For example:程序員

public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;
}

優點:緩存

  • 有名稱
  • 沒必要在每次調用它們的時候都建立一個新對象
  • 它們能夠返回原返回類型的任何子類型的對象
  • 在建立參數化類型實例的時候,他們使代碼變得更加簡潔

缺點:安全

  • 類若是不含公有的或者受保護的構造器,就不能被子類化
  • 它們與其餘的靜態方法實際上沒有任何區別

第二條:遇到多個構造器參數時要考慮用構造器

對於多個參數的構造器或者靜態方法,通常習慣採用重疊構造器(telescoping constructor)模式app

//Telescoping constructor pattern
public class PizzaIngredients{
    private final int salt; //optional
    private final int chess; //o
    private final int sausage; //o
    private final int flour; // required
    private final int water; //r
    ...
    
    public PizzaIngredients(int flour, int water){
        this(flour, water, 0);
    }
    public PizzaIngredients(int flour, int water, int salt){
        this(flour, water, salt, 0);
    }
    public PizzaIngredients(int flour, int water, int salt, int chess){
        this(flour, water, salt, chess, 0);
    }
    public PizzaIngredients(int flour, int water, int salt, int chess, int sausage){
        this.flour = flour;
        this.water= water;
        this.salt= salt;
        this.chess= chess;
        this.sausage= sausage;       
    }
}

若是構造參數太多這種方式代碼也會變得很難編寫,且不易閱讀。性能

第二種方式是採用JavaBeans模式,即設置每一個參數的默認值,並給每一個field加上setter方法,要設置某個值時直接調用其對應的setter方法。可是JavaBean在構造過程當中可能會處於不一致狀態,而且JavaBeans模式阻止了把類作成不可變的可能,這就須要付出額外的努力來確保它的線程安全。ui

第三種替代方法是Builder模式。客戶端利用必要的參數獲得一個builder對象,在builder對象上調用相似setter的方法,來設置每一個相關的可選參數,最後調用無參的build方法來生成不可變對象。this

public class PizzaIngredients{
    private final int salt; //optional
    private final int chess; //o
    private final int sausage; //o
    private final int flour; // required
    private final int water; //r
    ...
    
    public static class Builder{
      // required parameters
      private final int flour;
      private final int water;
      // optional
      private int salt = 0;
      private int chess = 0;
      private int sausage = 0;
      
      public Builder(int flour, int water){
          this.flour = flour;
          this.water= water;
      }
      public Builder salt(int val){
          salt = val;
          return this;
      }
      public Builder chess(int val){
          chess = val;
          return this;
      }
      public Builder sausage (int val){
          sausage = val;
          return this;       
      }
      public PizzaIngredients build(){
         return new PizzaIngredients (this)
      }
  }
  
  private
PizzaIngredients(Builder builder){
    flour = builder.flour;
    water = builder.water;
    salt = builder.salt;
    chess = builder.chess;
    sausage = builder.sausage;
  }
}

// 客戶端代碼
PizzaIngredients pizza = new PizzaIngredients.Builder(200, 150).salt(5).chess(20).sausage(25).build();

Builder模式優點在於參數可變,並且不須要考慮順序。可是Builder模式比重疊構造器模式更加冗長,最好在很對參數是使用。spa

第三條:用私有構造器或者枚舉類型強化Singleton屬性

 三種方式實現Singleton:線程

1. 公有靜態成員是final域code

//Singleton with public final field
public class Wills{
    public static final Wills INSTANCE = new Wills();
    private Wills(){
    };
    ...
}

2. 公有成員是靜態工廠方法

//Singleton with static factory
public class Wills{
    private static final Wills INSTANCE = new Wills();
    private Wills(){
    };
    public static Wills getInstance(){
        return INSTANCE;
}
    ...
}

這兩種方式在存在藉助AccessibleObject.setAccessible方法,經過方式機制調用私有方法的可能。爲抵禦這種攻擊,能夠修改構造器,在實例被第二次建立時拋出異常。

而且每次反序列化一個序列化的實例時,都會建立一個新的實例,這須要聲明全部實例域都是transient,並提供一個readResolve方法。

3. 單元素枚舉類型

// Enum singleton - the preferred approach
public enum Wills{
    INSTANCE;
    ...
}

這種方法在功能上相似公有域方法,但它更加簡潔並且無償的提供了序列化機制,絕對防止屢次實例化。單元素枚舉類型已經成爲實現Singleton的最佳方法。

第四條:經過私有構造器強化不可實例化的能力

不須要被實例化的類,能夠經過私有構造器使其不能被實例化。

Public class UtilityClass {
    private UtilityClass(){
        throw new AssertionError();
    }
}

 

第五條:避免建立沒必要要的對象

除了不可變的對象,也能夠重用已知不會修改的對象。
可使用靜態初始化器(initializer)來初始化代碼塊。

Public class Person{
    private final static String name;
    private final static String gender;

    static {
        Calendar cal = Calendar.getInstance(Timezone.getTimeZone("GMT"));
        ...
        ...
}

第六條:消除過時的對象引用

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack(){
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e){
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop(){
        if(size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    private void ensureCapacity() {
        if(elements.length == size){
            elements = Arrays.copyOf(elements, 2 * DEFAULT_INITIAL_CAPACITY + 1);
        }
    }
}

這段代碼中並無明顯的錯誤,可是這個程序中隱藏了一個問題,有一個「內存泄漏」,隨着垃圾回回收器活動的增長,或者因爲內存佔用的不斷增長,程序性能的下降會逐漸表現出來。

若是一個棧先是增加,而後再收縮,那麼從棧中彈出來的對象將不會被當作垃圾回收,即便使用棧的程序再也不引用這些對象,他們也不會被回收。這是由於,棧內部維護着對這些引用的過時引用(obsolete reference)。所謂過時引用,是指永遠也不會再被解除的引用。修復這類問題的方法很簡單,一旦對象引用已通過期,只需清空這些引用便可。對於本例,以下修改:

    public Object pop(){
        if(size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; return result;
    }

清空對象引用應該是一種例外,而不是一種規範行爲。

通常而言,只要是本身管理內存,程序員就應該警戒內存泄漏問題。

內存泄漏的另外一個常見來源是緩存。

內存泄漏的第三個常見來源是監聽器和其餘回調。

相關文章
相關標籤/搜索