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

