跟著 Guava 學(xué) Java 之 不可變集合
什么是不可變集合
不可變集合,英文叫 immutable,顧名思義就是說集合是不可被修改的。集合的數(shù)據(jù)項(xiàng)是在創(chuàng)建的時(shí)候提供,并且在整個(gè)生命周期中都不可改變。
為什么要用不可變集合?
第一:防御性編程需要
我有一個(gè)集合,你拿來使用,鬼知道你會(huì)不會(huì)亂搞,往集合里添加不合適的元素,或者隨便刪除元素,我不放心,對,就是不信你,我的集合我做主,給你個(gè)不可變的吧,這樣你就不可能亂搞我的集合了,我就放心了,不擔(dān)心你的操作給我?guī)盹L(fēng)險(xiǎn) 。官方解釋:防御,defensive programming,聽起來高級不?
第二:線程安全
沒有買賣就沒有殺害!
集合是不可變的,不讓你有變化,不可能有變化。沒有變化,就沒有競態(tài)條件,多少個(gè)線程來都是一個(gè)樣,安全,就是***安全。
第三:節(jié)省開銷
不需要支持可變性,可以盡量節(jié)省空間和時(shí)間的開銷, 所有的不可變集合實(shí)現(xiàn)都比可變集合更加有效的利用內(nèi)存。
JDK9 之前的實(shí)現(xiàn)
Collections提供了一組方法把可變集合封裝成不可變集合:
但這玩意兒有問題,舉個(gè)例子:
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);
這個(gè)輸出的結(jié)果居然是 [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] 果然坑爹,而且你發(fā)現(xiàn)沒有,編碼也比較麻煩,還得用 Collections 間接轉(zhuǎn)一下。
Collections.unmodifiableList 實(shí)現(xiàn)的不是真正的不可變集合,當(dāng)原始集合修改后,不可變集合也發(fā)生變化。此外,它返回的數(shù)據(jù)結(jié)構(gòu)本質(zhì)仍舊是原來的集合類,所以它的操作開銷,包括并發(fā)下修改檢查,hash table 里的額外數(shù)據(jù)空間都和原來的集合是一樣的。
由于這些問題,JDK9 出了些新的生成不可變集合的方法,比如:
- List.of
- Set.of
- Map.of
- ......
確實(shí)可以直接生成不可變集合,編碼也比較方便了:
List<String> immutableList= List.of("a", "b", "c");
如果你要修改集合會(huì)拋出異常 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];
當(dāng)然我們不是說人家 api 是錯(cuò)的,人家就是這么設(shè)計(jì)的(愛信不信),可我感覺不爽,如果不小心可能會(huì)犯錯(cuò),本來是防御性編程,搞不好干成跑路性編程了。
再次強(qiáng)調(diào),不是說人家 JDK 設(shè)計(jì)錯(cuò)了,人家就是這么設(shè)計(jì)的,你的明白?當(dāng)然不爽的還有 google 的工程師們,所以我們下面介紹下拿起鍵盤自己解決問題的 google 工程師們寫的 guava 是怎么解決問題的。
Guava
來,我們接著上面的那個(gè)例子,直接寫個(gè) Guava 版本的你自己體會(huì)下:
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] 了。
無論是從命名、語義、結(jié)果、代碼可讀性是不是都比 JDK 版本的好很多?這樣的代碼讓我感覺世界又美好了一些。
美好的東西都想擁有,但問題來了, Guava 針對哪些集合提供了哪些對應(yīng)的不可變集合類呢,這里我們給大家整理了一下:
可變集合接口 | 屬于 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 |
介紹幾個(gè)方法:
- of 方法,用法是一脈相承的,就是構(gòu)建集合用的
- copyOf ,上面例子中出現(xiàn)過,官方文檔上說它是智能的,比如它可以判斷參數(shù)是不是一個(gè) 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],主要是因?yàn)?copyOf 方法是 10 以上版本才有的。
你看,JDK 也一直在進(jìn)步,所以如果你用的是 JDK10 以及上版本,是不是要用 Guava 在這個(gè)具體功能點(diǎn)上就是可選的了。
最后
整體對比起來,我的個(gè)人感覺是在不可變集合的操作上 Guava 的 API 更好用一些,當(dāng)然庫的使用因人而異,用 JDK 原生的也沒毛病,畢竟依賴更少,學(xué)習(xí)成本也小。
我們總說要改革、要進(jìn)步,而真正的改革往往都不是自上而下的,很多都是自下而上的被推動(dòng)著前進(jìn) ,如果沒有 Guava,沒有開源社區(qū)的很多優(yōu)秀的庫和組件,JDK 會(huì)不會(huì)把這些優(yōu)秀的建議吸取進(jìn)來?我不知道,但至少 JAVA 也一直在進(jìn)步,也希望它越來越好。