跟著 Guava 學 Java 之 不可變集合
什么是不可變集合
不可變集合,英文叫 immutable,顧名思義就是說集合是不可被修改的。集合的數據項是在創建的時候提供,并且在整個生命周期中都不可改變。
為什么要用不可變集合?
第一:防御性編程需要
我有一個集合,你拿來使用,鬼知道你會不會亂搞,往集合里添加不合適的元素,或者隨便刪除元素,我不放心,對,就是不信你,我的集合我做主,給你個不可變的吧,這樣你就不可能亂搞我的集合了,我就放心了,不擔心你的操作給我帶來風險 。官方解釋:防御,defensive programming,聽起來高級不?
第二:線程安全
沒有買賣就沒有殺害!
集合是不可變的,不讓你有變化,不可能有變化。沒有變化,就沒有競態條件,多少個線程來都是一個樣,安全,就是***安全。
第三:節省開銷
不需要支持可變性,可以盡量節省空間和時間的開銷, 所有的不可變集合實現都比可變集合更加有效的利用內存。
JDK9 之前的實現
Collections提供了一組方法把可變集合封裝成不可變集合:
但這玩意兒有問題,舉個例子:
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
List<String> unmodifiableList = Collections.unmodifiableList(list);
list.add("d");
System.out.println(unmodifiableList);
這個輸出的結果居然是 [a,b,c,d]。
what ? 這不就變了嗎,我要的是不可變集合啊,這坑爹的玩意兒。有兄弟說了,那我切斷 list 的引用是不就行了?
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
List<String> unmodifiableList = Collections.unmodifiableList(list);
list.add("d");
list = null;
System.out.println(unmodifiableList);
呵呵,不行,輸出仍然是 [a,b,c,d] 果然坑爹,而且你發現沒有,編碼也比較麻煩,還得用 Collections 間接轉一下。
Collections.unmodifiableList 實現的不是真正的不可變集合,當原始集合修改后,不可變集合也發生變化。此外,它返回的數據結構本質仍舊是原來的集合類,所以它的操作開銷,包括并發下修改檢查,hash table 里的額外數據空間都和原來的集合是一樣的。
由于這些問題,JDK9 出了些新的生成不可變集合的方法,比如:
- List.of
- Set.of
- Map.of
- ......
確實可以直接生成不可變集合,編碼也比較方便了:
List<String> immutableList= List.of("a", "b", "c");
如果你要修改集合會拋出異常 java.lang.UnsupportedOperationException:
immutableList.add("d");
but;
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
List<List<String>> list1 = List.of(list);
list.add("d");
System.out.println(list1);
上面代碼的輸出仍然是 : [a,b,c,d];
當然我們不是說人家 api 是錯的,人家就是這么設計的(愛信不信),可我感覺不爽,如果不小心可能會犯錯,本來是防御性編程,搞不好干成跑路性編程了。
再次強調,不是說人家 JDK 設計錯了,人家就是這么設計的,你的明白?當然不爽的還有 google 的工程師們,所以我們下面介紹下拿起鍵盤自己解決問題的 google 工程師們寫的 guava 是怎么解決問題的。
Guava
來,我們接著上面的那個例子,直接寫個 Guava 版本的你自己體會下:
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
ImmutableList<String> strings = ImmutableList.copyOf(list);
list.add("d");
System.out.println(strings);
輸出終于如我所愿的是 : [a,b,c] 了。
無論是從命名、語義、結果、代碼可讀性是不是都比 JDK 版本的好很多?這樣的代碼讓我感覺世界又美好了一些。
美好的東西都想擁有,但問題來了, Guava 針對哪些集合提供了哪些對應的不可變集合類呢,這里我們給大家整理了一下:
可變集合接口 | 屬于 JDK 還是 Guava | 不可變版本 |
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | ImmutableSortedMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |
介紹幾個方法:
- of 方法,用法是一脈相承的,就是構建集合用的
- copyOf ,上面例子中出現過,官方文檔上說它是智能的,比如它可以判斷參數是不是一個 immutable 對象,這樣可以避免做拷貝
JDK10
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
List<String> strings = List.copyOf(list);
list.add("d");
System.out.println(strings);
以上代碼在 JDK10 以上版本輸出 :[a,b,c],主要是因為 copyOf 方法是 10 以上版本才有的。
你看,JDK 也一直在進步,所以如果你用的是 JDK10 以及上版本,是不是要用 Guava 在這個具體功能點上就是可選的了。
最后
整體對比起來,我的個人感覺是在不可變集合的操作上 Guava 的 API 更好用一些,當然庫的使用因人而異,用 JDK 原生的也沒毛病,畢竟依賴更少,學習成本也小。
我們總說要改革、要進步,而真正的改革往往都不是自上而下的,很多都是自下而上的被推動著前進 ,如果沒有 Guava,沒有開源社區的很多優秀的庫和組件,JDK 會不會把這些優秀的建議吸取進來?我不知道,但至少 JAVA 也一直在進步,也希望它越來越好。