項目15:最小化變動性

不變性類別(immutable class)的實例是無法修改的,所有實例的資訊是在產生時就提供的,在它的物件的生命週期中是固定的。不變性類別更容易設計、實作跟使用,減少出錯而且更安全。

建立不變性類別的五個準則:

  1. 不提供任何可以變更物件狀態的方法(mutators)。
  2. 確保類別不能被繼承。
  3. 所有屬性設為final:在沒有同步的清況下,新建的實例傳到另一個執行緒的時候,如果有一個參考指向它,需確保行為的正確性。
  4. 所有屬性設為private:避免用戶直接存取該屬性。
  5. 每個可變的元素,都有專門的存取方式:確保用戶無法取得類別中任何指向可變性物件的屬性的參考(reference),在建構子、存取方法使用防禦複制(defensive copy)。
public final class Complex { 
    private final double re;
    private final double im;
    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    // Accessors with no corresponding mutators
    public double realPart()      { return re; }
    public double imaginaryPart() { return im; }
    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }
    public Complex subtract(Complex c) {
           return new Complex(re - c.re, im - c.im);
    }
    public Complex multiply(Complex c) {
           return new Complex(re * c.re - im * c.im,
                              re * c.im + im * c.re);
    }
    public Complex divide(Complex c) {
           double tmp = c.re * c.re + c.im * c.im;
           return new Complex((re * c.re + im * c.im) / tmp,
                              (im * c.re - re * c.im) / tmp);
    }
    @Override public boolean equals(Object o) {
          if (o == this)
              return true;
          if (!(o instanceof Complex))
              return false;
          Complex c = (Complex) o;
// See page 43 to find out why we use compare instead of == return Double.compare(re, c.re) == 0 &&
                 Double.compare(im, c.im) == 0;
    }
   @Override public int hashCode() {
       int result = 17 + hashDouble(re);
       result = 31 * result + hashDouble(im);
       return result;
    }
    private int hashDouble(double val) {
           long longBits = Double.doubleToLongBits(re);
           return (int) (longBits ^ (longBits >>> 32));
    }
    @Override public String toString() {
           return "(" + re + " + " + im + "i)";
    }
}

功能方法(functional approach)模式:對運算的對象使用function,但不修改它,並將結果回傳。

  1. 不變性物件很簡單:不變性物件打從建立時,它的狀態就是固定的,假如你確保所有建構子保持類別的不變性,可減少所有使用該類別的程式設計人員的麻煩。
  2. 不變性物件有執行緒安全(thread-safe)的特性,不需同步:無疑是最簡單達到執行緒安全的方式。不變性物件應該利用此特性,讓用戶盡可能重複使用既存的實例。

    public static final Complex ZERO = new Complex(0, 0);
    public static final Complex ONE  = new Complex(1, 0);
    public static final Complex I    = new Complex(0, 1);
    

    進一步的做法是提供靜態工廠(static factory),將常用的實例cache起來,避免產生新的實例。 不變性物件不需要使用防禦複制,因為它的複制品是相同的實例,所以,這些不變性類別不應該提供clone方法和複制建構子(copy constructor)。

  3. 除了分享不變性物件,也可以共享其內部:例如,BigInteger類別使用sign-magnitude,符號由一個int表示,大小由一個int陣列呈現,BigInteger.negate()方法建立的實列就是指向同一個int陣列,而不是複制它
  4. Immutable objects make great building blocks for other objects
  5. 唯一的缺點是不同的值需要不同的物件:例如你有個百萬bit大小的BigInteger,而你只是想修改它的低位數

     BigInteger moby = ...;
     moby = moby.flipBit(0);
    

    flipBit()方法建立一個一樣百萬大小的BigInteger,但跟原本的物件只差一個bit。相反地,BitSet是可變性的,它提供一個方法用來更改單一位數的值。

    假如你每一步都產生一個新的物件,那效能的問題是會被放大的,這裡有兩個解決的方法。首先,用基本型態(primitive)取代那些經常性的運算,那就不需要一直產生新的物件,比如提供一個public mutable companion class,像String就可以使用StirngBuilder,而BigSet對BigInteger來說就是這樣的角色。

另外,除了禁止繼承來維持immutable,還可以透過將建構子設為private或package-private,新增public static factory替代public建構子

// Immutable class with static factories instead of constructors
public class Complex {
    private final double re;
    private final double im;
    private Complex(double re, double im) {     this.re = re;
        this.im = im;
    }
    public static Complex valueOf(double re, double im) { 
        return new Complex(re, im);
    }
       ... // Remainder unchanged
}

results matching ""

    No results matching ""