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ù)測性:集合狀態(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庫提供了更靈活的創(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)
適用場景:
- 配置數(shù)據(jù):如國家代碼列表
- 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)測試數(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)不可變集合的三種核心方式各有適用場景:
| 實(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)文章
運(yùn)行時(shí)常量池和字符串常量池的區(qū)別及說明
這篇文章主要介紹了運(yùn)行時(shí)常量池和字符串常量池的區(qū)別及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
JAVA8 stream中三個(gè)參數(shù)的reduce方法對(duì)List進(jìn)行分組統(tǒng)計(jì)操作
這篇文章主要介紹了JAVA8 stream中三個(gè)參數(shù)的reduce方法對(duì)List進(jìn)行分組統(tǒng)計(jì)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
淺談MyBatis通用Mapper實(shí)現(xiàn)原理
這篇文章主要介紹了淺談MyBatis通用Mapper實(shí)現(xiàn)原理,本文會(huì)先介紹通用 Mapper 的簡單原理,然后使用最簡單的代碼來實(shí)現(xiàn)這個(gè)過程。感興趣的小伙伴們可以參考一下2018-10-10
HarmonyOS實(shí)現(xiàn)Java端類似Nine-Patch氣泡聊天框代碼
在HarmonyOS Java端實(shí)現(xiàn)氣泡聊天框,與Android 上的9圖(Nine-Patch)有相似的實(shí)現(xiàn)方式,在HarmonyOS中,可以使用ShapeElement和ElementContainer來創(chuàng)建和管理可伸縮的氣泡背景,下面提供一個(gè)簡單的示例代碼,可以在 HarmonyOS 中實(shí)現(xiàn)類似于Android的Nine-Patch氣泡聊天框效果2024-07-07
Java 中的Function.identity()實(shí)現(xiàn)原理解析
Function.identity()是Java 8 引入的一個(gè)靜態(tài)方法,屬于 java.util.function.Function 函數(shù)式接口,這篇文章主要介紹了Java 中的Function.identity()實(shí)現(xiàn)原理,需要的朋友可以參考下2025-08-08
Java中Set集合遍歷的四種方法實(shí)現(xiàn)
本文主要介紹了Java中Set集合遍歷的四種方法實(shí)現(xiàn),包括迭代器遍歷、增強(qiáng)型for循環(huán)遍歷、Stream API遍歷以及TreeSet的有序遍歷,具有一定的參考價(jià)值,感興趣的可以了解一下2025-05-05
解決springboot啟動(dòng)失敗的問題('hibernate.dialect'?not?set)
這篇文章主要介紹了解決springboot啟動(dòng)失敗的問題('hibernate.dialect'?not?set),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12

