特別感謝:慕課網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併發編程與高併發解決方案