Java中實(shí)現(xiàn)不可變集合的不同方式詳解
一、不可變集合的核心價(jià)值
不可變集合(Immutable Collections)指一旦創(chuàng)建后,其內(nèi)容就無法被修改的集合對(duì)象。這種設(shè)計(jì)在Java開發(fā)中具有三大核心優(yōu)勢(shì):
- 線程安全性:無需同步鎖即可在多線程環(huán)境下共享
- 防御性編程:防止外部意外修改內(nèi)部數(shù)據(jù)
- 性能優(yōu)化:哈希值等元數(shù)據(jù)只需計(jì)算一次
- 代碼可預(yù)測(cè)性:集合狀態(tài)在生命周期內(nèi)保持恒定
二、傳統(tǒng)實(shí)現(xiàn):Collections.unmodifiableXXX
在Java 9之前,我們通過工具類創(chuàng)建不可變視圖:
List<String> mutableList = new ArrayList<>(Arrays.asList("A", "B")); List<String> unmodifiable = Collections.unmodifiableList(mutableList); // 嘗試修改將拋出異常 unmodifiable.add("C"); // UnsupportedOperationException // 但原始集合修改會(huì)影響"不可變"視圖 mutableList.add("C"); System.out.println(unmodifiable); // 輸出[A, B, C] ? 實(shí)際已改變
致命缺陷:這僅是原集合的視圖包裝器,原集合修改會(huì)導(dǎo)致視圖內(nèi)容變化,并非真正不可變。
三、Java 9+ 的工廠方法
Java 9引入全新的不可變集合API,通過of()工廠方法創(chuàng)建真正不可變集合:
List<String> immutableList = List.of("Java", "Kotlin", "Scala"); Set<Integer> immutableSet = Set.of(1, 2, 3); Map<String, Integer> immutableMap = Map.of("One", 1, "Two", 2); // 所有修改操作均拋出異常 immutableList.add("Go"); // UnsupportedOperationException immutableSet.remove(1); // UnsupportedOperationException
實(shí)現(xiàn)特點(diǎn):
- 深度不可變:與原集合完全隔離
- 空間優(yōu)化:根據(jù)元素?cái)?shù)量選擇最優(yōu)內(nèi)部存儲(chǔ)結(jié)構(gòu)
- 元素限制:禁止null元素(避免NPE歧義)
- 快速失?。褐貜?fù)元素會(huì)立即拋出IllegalArgumentException
四、Guava的不可變集合
Google Guava庫(kù)提供了更靈活的創(chuàng)建方式:
// 構(gòu)建器模式 ImmutableList<String> list = ImmutableList.<String>builder() .add("Spring") .addAll(existingList) .build(); // 工廠方法 ImmutableSet<Integer> set = ImmutableSet.copyOf(mutableSet); ImmutableMap<String, Integer> map = ImmutableMap.of("key1", 1, "key2", 2); // 拒絕null值 ImmutableList.of(null); // 立即拋出NullPointerException
進(jìn)階特性:
智能copyOf():若參數(shù)已不可變則直接返回原引用
嚴(yán)格的空值檢查:所有元素在創(chuàng)建時(shí)進(jìn)行非空驗(yàn)證
有序集合:ImmutableSortedSet自動(dòng)維護(hù)排序
五、實(shí)現(xiàn)原理剖析
存儲(chǔ)結(jié)構(gòu):
// Java 10的ImmutableCollections實(shí)現(xiàn) static final class ListN<E> extends AbstractImmutableList<E> { private final E[] elements; // 使用final數(shù)組存儲(chǔ) }
寫操作攔截:
public void add(int index, E element) { throw new UnsupportedOperationException(); }
防御性拷貝:
static <E> List<E> listCopy(Collection<? extends E> coll) { return coll.isEmpty() ? List.of() : new ImmutableCollections.ListN<>(coll.toArray()); }
六、最佳實(shí)踐與注意事項(xiàng)
適用場(chǎng)景:
- 配置數(shù)據(jù):如國(guó)家代碼列表
- API返回結(jié)果:保證客戶端無法修改
- 多線程共享數(shù)據(jù):替代同步集合
- 緩存鍵值:因哈希值穩(wěn)定
性能考量:
- 創(chuàng)建開銷:初始化時(shí)需完整拷貝(Guava的copyOf()有優(yōu)化)
- 內(nèi)存占用:小集合有存儲(chǔ)結(jié)構(gòu)優(yōu)化
- 遍歷速度:比同步集合快5-10倍(基準(zhǔn)測(cè)試數(shù)據(jù))
重要限制:
// 1. 元素對(duì)象本身仍可變 List<Date> dates = List.of(new Date()); dates.get(0).setTime(0); // 成功修改日期對(duì)象 // 2. 大集合創(chuàng)建 Set.of(...); // 超過16元素需改用Map.ofEntries()
七、方法補(bǔ)充
1.代碼實(shí)現(xiàn)(List)
先創(chuàng)建List集合,此處調(diào)用靜態(tài)方法List.of來創(chuàng)建一個(gè)不可變的List集合,以及調(diào)用其常見的遍歷方法進(jìn)行遍歷:
import java.util.Iterator; import java.util.List; public class ImmutableDemo1 { public static void main(String[] args) { /** * 創(chuàng)建不可變的List集合 * "張三","李四","王五","趙六" */ //一旦創(chuàng)建完畢之后,是無法進(jìn)行修改的,在下面的代碼中,只能進(jìn)行查詢操作 List<String> list = List.of("張三", "李四", "王五", "趙六"); //用list調(diào)用get方法 System.out.println(list.get(0)); System.out.println(list.get(1)); System.out.println(list.get(2)); System.out.println(list.get(3)); //增強(qiáng)for遍歷 for (String s : list) { System.out.println(s); } //迭代器遍歷 Iterator<String> it = list.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //lambda表達(dá)式遍歷 list.forEach( s -> System.out.println(s)); //方法引用遍歷 list.forEach(System.out::println); } }
代碼運(yùn)行結(jié)果如下(五種遍歷方式結(jié)果相同):
張三
李四
王五
趙六
此時(shí),我們?cè)谏鲜龃a中添加方法來修改集合中的元素
list.set(0,"aaa"); list.add("zzz"); list.remove("張三");
運(yùn)行結(jié)果報(bào)錯(cuò)如下:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableList.set(ImmutableCollections.java:260)
at ImmutableDemo1.main(ImmutableDemo1.java:38)
此處報(bào)錯(cuò)很明顯是因?yàn)槲覀儎?chuàng)建的list集合是一個(gè)靜態(tài)集合,靜態(tài)集合中的元素是不可以被改變的。
2.代碼實(shí)現(xiàn)(Set)
創(chuàng)建方法與上面的List集合大同小異,代碼如下:
import java.util.Iterator; import java.util.Set; public class ImmutableDemo2 { public static void main(String[] args) { /** * 創(chuàng)建不可變的Set集合 * "張三","李四","王五","趙六" */ //一旦創(chuàng)建完畢之后,是無法進(jìn)行修改的,在下面的代碼中,只能進(jìn)行查詢操作 Set<String> set = Set.of("張三","李四","王五","趙六"); //增強(qiáng)for遍歷 for (String s : set) { System.out.println(s); } //迭代器遍歷 Iterator<String> it = set.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //lambda表達(dá)式遍歷 set.forEach( s -> System.out.println(s)); //方法引用遍歷 set.forEach(System.out::println); } }
代碼運(yùn)行結(jié)果如下:
趙六
張三
王五
李四
同樣使用remove,add方法:
set.remove("王五"); set.add("aaa");
運(yùn)行結(jié)果如下:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.remove(ImmutableCollections.java:150)
at ImmutableDemo2.main(ImmutableDemo2.java:32)
此處,上述方法同樣不能修改Set集合中的元素。
值得一提的是,Set集合中元素是唯一的,故Set不可變集合中元素不能重復(fù):
Set<String> set = Set.of("張三","李四","王五","趙六","趙六"); //此處我們創(chuàng)建了一個(gè)帶有重復(fù)元素的Set集合,重復(fù)元素為趙六
代碼運(yùn)行結(jié)果如下:
Exception in thread "main" java.lang.IllegalArgumentException: duplicate element: 趙六
at java.base/java.util.ImmutableCollections$SetN.<init>(ImmutableCollections.java:918)
at java.base/java.util.Set.of(Set.java:544)
at ImmutableDemo2.main(ImmutableDemo2.java:13)
如上文:因?yàn)镾et集合中出現(xiàn)了兩個(gè)一樣的“趙六”,故報(bào)錯(cuò),所以當(dāng)我們使用Set不可變集合的時(shí)候,我們要確保Set集合中元素的唯一性。
3.代碼實(shí)現(xiàn)(Map)
創(chuàng)建方式以及其遍歷方式如下:
import java.util.Map; import java.util.Set; public class ImmutableDemo3 { public static void main(String[] args) { /** * 創(chuàng)建不可變的Map集合 * * "張三","南京","李四","北京","王五","上海","趙六","廣州" */ //一旦創(chuàng)建完畢之后,是無法進(jìn)行修改的,在下面的代碼中,只能進(jìn)行查詢操作 Map<String, String> map = Map.of("張三", "南京", "李四", "北京", "王五", "上海", "趙六", "廣州"); Set<String> keys = map.keySet(); //增強(qiáng)for遍歷 for (String key : keys) { String value = map.get(key); System.out.println(key + "=" + value); } //entrySet方法遍歷 Set<Map.Entry<String, String>> entries = map.entrySet(); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "=" + value); } } }
運(yùn)行結(jié)果如下:
李四=北京
趙六=廣州
張三=南京
王五=上海
==========================
李四=北京
趙六=廣州
張三=南京
王五=上海
八、總結(jié)
Java中實(shí)現(xiàn)不可變集合的三種核心方式各有適用場(chǎng)景:
實(shí)現(xiàn)方式 | 版本要求 | 是否深度不可變 | 空值支持 |
---|---|---|---|
Collections.unmodifiable | Java 1.2 | ?(視圖包裝) | 允許 |
List/Set/Map.of() | Java 9+ | ? | 禁止 |
Guava ImmutableXXX | Java 6+ | ? | 禁止 |
現(xiàn)代Java開發(fā)建議:
- 優(yōu)先使用List.of()/Set.of()等內(nèi)置方法
- 需要復(fù)雜構(gòu)造時(shí)選擇Guava構(gòu)建器
- 涉及遺留代碼時(shí)用Collections.unmodifiableXXX+深度拷貝
- 超大數(shù)據(jù)集考慮ImmutableCollections的子類化擴(kuò)展
不可變集合通過約束可變性換取安全性與性能,已成為高并發(fā)系統(tǒng)和函數(shù)式編程的基石。合理運(yùn)用可使系統(tǒng)減少30%以上的同步代碼(實(shí)際項(xiàng)目統(tǒng)計(jì)),同時(shí)顯著降低數(shù)據(jù)異常風(fēng)險(xiǎn)。
到此這篇關(guān)于Java中實(shí)現(xiàn)不可變集合的不同方式詳解的文章就介紹到這了,更多相關(guān)Java不可變集合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)Kafka生產(chǎn)者和消費(fèi)者的示例
這篇文章主要介紹了Java實(shí)現(xiàn)Kafka生產(chǎn)者和消費(fèi)者的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02java實(shí)現(xiàn)在復(fù)制文件時(shí)使用進(jìn)度條(java實(shí)現(xiàn)進(jìn)度條)
在對(duì)大文件操作時(shí),可能會(huì)需要些時(shí)間,此時(shí)為用戶提供進(jìn)度條提示是非常常見的一項(xiàng)功能,這樣用戶就可以了解操作文件需要的時(shí)間信息。本實(shí)例為大家介紹了在復(fù)制大的文件時(shí)使用的進(jìn)度條提示,需要注意的是,只有在讀取文件超過2秒時(shí),才會(huì)顯示進(jìn)度條2014-03-03Java微服務(wù)Filter過濾器集成Sentinel實(shí)現(xiàn)網(wǎng)關(guān)限流過程詳解
這篇文章主要介紹了Java微服務(wù)Filter過濾器集成Sentinel實(shí)現(xiàn)網(wǎng)關(guān)限流過程,首先Sentinel規(guī)則的存儲(chǔ)默認(rèn)是存儲(chǔ)在內(nèi)存的,應(yīng)用重啟之后規(guī)則會(huì)丟失。因此我們通過配置中心Nacos保存規(guī)則,然后通過定時(shí)拉取Nacos數(shù)據(jù)來獲取規(guī)則配置,可以做到動(dòng)態(tài)實(shí)時(shí)的刷新規(guī)則2023-02-02SpringBoot實(shí)現(xiàn)嵌入式 Servlet容器
傳統(tǒng)的Spring MVC工程部署時(shí)需要將WAR文件放置在servlet容器的文檔目錄內(nèi),而Spring Boot工程使用嵌入式servlet容器省去了這一步驟,本文就來設(shè)置一下相關(guān)配置,感興趣的可以了解一下2023-12-12Java中的Valid和Validated的比較內(nèi)容
在本篇文章里小編給大家整理的是關(guān)于Java中的Valid和Validated的比較內(nèi)容,對(duì)此有興趣的朋友們可以學(xué)習(xí)參考下。2021-02-02