慕課網實戰·高併發探索(六):不可變對象 -final -ImmutableX -unmodifiableX

特別感謝:慕課網jimin老師的《Java併發編程與高併發解決方案》課程,以下知識點多數來自老師的課程內容。
jimin老師課程地址:Java併發編程與高併發解決方案


概念

1、不可變對象

有一種對象只要它發佈了就是安全的,它就是不可變對象。一個不可變對象需要滿足的條件:

  • 對象創建一個其狀態不能修改
  • 對象所有域都是final類型
  • 對象是正確創建的(在對象創建期間,this引用沒有逸出)

2、創建一個不可變對象的方法

(1)自己定義
這裏可以採用的方式包括:
1、將類聲明爲final,這樣它就不能被繼承。
2、將所有的成員聲明爲私有的,這樣就不允許直接訪問這些成員。
3、對變量不提供set方法,將所有可變的成員聲明爲final,這樣就只能賦值一次。通過構造器初始化所有成員進行深度拷貝。
4、在get方法中不直接返回對象的本身,而是克隆對象,返回對象的拷貝。

(2)使用Java中提供的Collection類中的各種unmodifiable開頭的方法
(3)使用Guava中的Immutable開頭的類

3、final關鍵字

final關鍵字可以修飾類、修飾方法、修飾變量

  • 修飾類:類不能被集成。
    基礎類型的包裝類都是final類型的類。final類中的成員變量可以根據需要設置爲final,但是要注意的是,final類中的所有成員方法都會被隱式的指定爲final方法
  • 修飾方法:
    (1)把方法鎖定,以防任何繼承類修改它的含義
    (2)效率:在早期的java實現版本中,會將final方法轉爲內嵌調用。但是如果方法過於龐大,可能看不見效果。一個private方法會被隱式的指定爲final方法
  • 修飾變量:
    基本數據類型變量,在初始化之後,它的值就不能被修改了。如果是引用類型變量,在它初始化之後便不能再指向另外的對象。
    這裏寫圖片描述

從上圖我們可見,
(1)對一個被final修飾的變量(Integer a、String b)被賦值時在編譯過程中就出現了錯誤。
(2)(map)在重新被指向一個新的map對象的時候也出現了錯誤。
那麼對被定義爲final的map進行賦值呢?我們單獨運行map.put(1,3)語句,結果是可以的。被final修飾的引用類型變量,雖然不能重新指向,但是可以修改,這一點尤爲要注意。
這裏寫圖片描述
(3)當final修飾方法的參數時:同樣也是不允許在方法內部對其修改的。
這裏寫圖片描述

4、Java:unmodifiable相關方法

使用Java的Collection類的unmodifiable相關方法,可以創建不可變對象。unmodifiable相關方法包含:Collection、List、Map、Set….
這裏寫圖片描述
舉個栗子:

public class ImmutableExample {

    private static Map<Integer, Integer> map = Maps.newHashMap();

    static {
        map.put(1, 2);
        map.put(3, 4);
        map.put(5, 6);
        map = Collections.unmodifiableMap(map);
    }

    public static void main(String[] args) {
        map.put(1, 3);
        log.info("{}", map.get(1));
    }

}

上面程序的執行結果爲:在map.put(1,3)操作的位置拋出了異常。由此可見map對象已經成爲不可變對象。
這裏寫圖片描述

那麼unmodifiable相關類的實現原理是什麼呢?我們查看一下Collections.unmodifiableMap的源碼:(以下源碼只篩選出表達觀點的部分,非全部)

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
        return new UnmodifiableMap<>(m);
}
private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable {
    ...
    public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }
    ...
}

Collections.unmodifiableMap在執行時,將參數中的map對象進行了轉換,轉換爲Collection類中的內部類 UnmodifiableMap對象。而 UnmodifiableMap對map的更新方法(比如put、remove等)進行了重寫,均返回UnsupportedOperationException異常,這樣就做到了map對象的不可變。

5、Guava:Immutable相關類

使用Guava的Immutable相關類也可以創建不可變對象。同樣包含很多類型:Collection、List、Map、Set….
這裏寫圖片描述
舉栗子:
(1)ImmutableList

private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);

list.add(4);//這一句在書寫完就會被IDE提示該add方法爲過時方法,實際爲不可用方法

對於ImmutableList.of方法,如果傳多個參數,需要這樣一直寫下去,以逗號分隔每個參數。其源碼中是這樣實現的:

    //單個或少於12個參數時
    public static <E> ImmutableList<E> of() {
        return RegularImmutableList.EMPTY;
    }

    public static <E> ImmutableList<E> of(E element) {
        return new SingletonImmutableList(element);
    }

    public static <E> ImmutableList<E> of(E e1, E e2) {
        return construct(e1, e2);
    }

    public static <E> ImmutableList<E> of(E e1, E e2, E e3) {
        return construct(e1, e2, e3);
    }
    ....

    //多於12個參數時,參數列表中最後的E...other會以數組形式接收參數
    @SafeVarargs
    public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5,
     E e6, E e7, E e8, E e9, E e10, E e11, E e12, E... others) {
        Object[] array = new Object[12 + others.length];
        array[0] = e1;
        array[1] = e2;
        array[2] = e3;
        array[3] = e4;
        array[4] = e5;
        array[5] = e6;
        array[6] = e7;
        array[7] = e8;
        array[8] = e9;
        array[9] = e10;
        array[10] = e11;
        array[11] = e12;
        System.arraycopy(others, 0, array, 12, others.length);
        return construct(array);
    }

運行結果仍然爲拋出UnsupportedOperationException異常。
分析源碼:Immutable相關類使用了跟Java的unmodifiable相關類相似的實現方法。

/** @deprecated */
    @Deprecated
    @CanIgnoreReturnValue
    public final boolean add(E e) {
        throw new UnsupportedOperationException();
    }

(2)ImmutableSet
ImmutableSet除了使用of的方法進行初始化,還可以使用copyof方法,將Collection類型、Iterator類型作爲參數。

private final static ImmutableSet set = ImmutableSet.copyOf(list);

private final static ImmutableSet set = ImmutableSet.copyOf(list.iterator());

(3)ImmutableMap
ImmutableMap有特殊的builder寫法:

 private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);

 private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder()
            .put(1, 2).put(3, 4).put(5, 6).build();

特別感謝:慕課網jimin老師的《Java併發編程與高併發解決方案》課程,以上知識點多數來自老師的課程內容。
jimin老師課程地址:Java併發編程與高併發解決方案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章