Java中的不可變類理解

1、Java中的不可變類

不可變類(Immutable Objects):當類的實例一經建立,其內容便不可改變,即沒法修改其成員變量。html

可變類(Mutable Objects):類的實例建立後,能夠修改其內容。java

Java 中八個基本類型的包裝類和 String 類都屬於不可變類,而其餘的大多數類都屬於可變類。緩存

2、與引用不可變的區別

須要特別注意的是,不可變類的不可變是指該類的實例不可變而非指向該實例的引用的不可變安全

String s = "abc";
System.out.println("s:" + s);  // 輸出s:abc
s = "xyz";
System.out.println("s:" + s);  // 輸出s:xyz

以上代碼顯示,不可變類 String 貌似是能夠改變值的,但實際上並非。變量 s 只是一個指向 String 類的實例的引用,存儲的是實例對象在內存中的地址。代碼中第三行的 「改變」 其實是新實例化了一個 String 對象,並將 s 的指向修改到新對象上,而原來的對象在內存中並未發生變化,只是少了一個指向它的引用,而且在將來被垃圾回收前它都將保持不變。多線程

public class Immutable {
 
    public static void main(String[] args) {
        String str = new String("abc");
        String str2 = str;
        System.out.println(str == str2); // true
        str2 = "cba";
        System.out.println(str == str2); // false
 
        System.out.println(str == row(str)); // true
        System.out.println(str == other(str)); // false
    }
 
    static private String row(String s){
        return s;
    }
    static private String other(String s){
        s="xyz"; //此處形參 s 指向了新的String對象,引用的地址發生變化
        return s;
    }
}

如此咱們看到,對於不可變類的對象,都是經過新建立一個對象並將引用指向新對象來實現變化的性能

一般,使用關鍵字 final 修飾的字段初始化後是不可變的,而這種不可變就是指引用的不可變。具體就是該引用所指對象的內存地址是不可變的,但並不是該對象不可變。若是該對象也不可變,那麼該對象就是不可變類的一個實例。this

public class Immutable {
  
    public static void main(String[] args) {
        Immutable immutable = new Immutable();
        final Inner inner = immutable.new Inner();
        inner.value = 123; // 實例可變
        // 下面語句編譯錯誤,inner 是final的,沒法讓它指向新的對象(改變指向地址)
        // inner = it.new Inner();
        Inner inner2 = inner; // 複製了一份引用,inner和inner2指向同一個對象
        System.out.println(inner); // 將調用 toString 方法輸出對象內存地址
        System.out.println(inner2); // inner和inner2具備相同的地址
        System.out.println(inner.value); // 輸出 123
        System.out.println(inner2.value); // 輸出123
        inner2.value = 321;
        System.out.println(inner); // 輸出321
    }
  
    class Inner{
        private int value;
    }
}

3、不可變類是如何實現的

immutable對象的狀態在建立以後就不能發生改變,任何對它的改變都應該產生一個新的對象。spa

所以,一個不可變類的定義應當具有如下特徵線程

  1. 全部成員都是 private final 的
  2. 不提供對成員的改變方法,例如:setXXXX
  3. 確保全部的方法不會被重載。手段有兩種:使用final Class(強不可變類),或者將全部類方法加上final(弱不可變類)。
  4. 若是某一個類成員不是基本類型(primitive type)或不可變類,必須經過在成員初始化(in)或者getter方法(out)時經過深度拷貝(即複製一個該類的新實例而非引用)方法,來確保類的不可變。
  5. 若是有必要,重寫hashCode和equals方法,同時應保證兩個用equals方法判斷爲相等的對象,其hashCode也應相等。

下面是一個示例:設計

public final class ImmutableDemo { 
    private final int[] myArray; 
    public ImmutableDemo(int[] array) {
    // this.myArray = array; // 錯誤!
    this.myArray = array.clone(); // 正確
  } 
  public int[] get(){
    return myArray.clone();
  }
}

上例中錯誤的方法不能保證不可變性,myArray 和形參 array 指向同一塊內存地址,用戶能夠在 ImmutableDemo 實例以外經過修改 array 對象的值來改變實例內部 myArray 的值。正確的作法是經過深拷貝將 array 的值傳遞給 myArray 。一樣, getter 方法中不能直接返回對象自己,而應該是克隆對象並返回對象的拷貝,這種作法避免了對象外泄,防止經過 getter 得到內部可變成員對象後對成員變量直接操做,致使成員變量發生改變。

對於不可變類,String 是一個典型例子,看看它的源碼也有助於咱們設計不可變類

4、不可變類的優勢

不可變類有兩個主要有點,效率和安全。

  • 效率

    當一個對象是不可變的,那麼須要拷貝這個對象的內容時,就不用複製它的自己而只是複製它的地址,複製地址(一般一個指針的大小)只須要很小的內存空間,具備很是高的效率。同時,對於引用該對象的其餘變量也不會形成影響。

    此外,不變性保證了hashCode 的惟一性,所以能夠放心地進行緩存而沒必要每次從新計算新的哈希碼。而哈希碼被頻繁地使用, 好比在hashMap 等容器中。將hashCode 緩存能夠提升以不變類實例爲key的容器的性能。

  • 線程安全

    在多線程狀況下,一個可變對象的值極可能被其餘進程改變,這樣會形成不可預期的結果,而使用不可變對象就能夠避免這種狀況同時省去了同步加鎖等過程,所以不可變類是線程安全的。

固然,不可變類也有缺點:不可變類的每一次「改變」都會產生新的對象,所以在使用中不可避免的會產生不少垃圾

 

更多關於深度拷貝的知識,參閱這篇:java對象深複製、淺複製(深拷貝、淺拷貝)的理解

相關文章
相關標籤/搜索