Java中Arrays.asList() 的不可變陷阱
一、問題現(xiàn)象:無法修改的集合
當(dāng)開發(fā)者使用 Arrays.asList()
轉(zhuǎn)換數(shù)組為集合時(shí),嘗試添加/刪除元素會拋出異常:
String[] arr = {"Java", "Python", "Go"}; List<String> list = Arrays.asList(arr); // 嘗試添加元素 list.add("JavaScript"); // 拋出 UnsupportedOperationException // 嘗試刪除元素 list.remove(0); // 同樣拋出異常
控制臺報(bào)錯(cuò):
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108)
二、原理剖析:為什么不可變?
2.1 源碼分析
// Arrays.java public static <T> List<T> asList(T... a) { return new ArrayList<>(a); // 注意:此ArrayList非java.util.ArrayList } // Arrays內(nèi)部的私有靜態(tài)類 private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private final E[] a; // final修飾的數(shù)組! ArrayList(E[] array) { a = Objects.requireNonNull(array); } // 未重寫add/remove方法(繼承AbstractList的默認(rèn)實(shí)現(xiàn)) } // AbstractList.java public void add(int index, E element) { throw new UnsupportedOperationException(); }
2.2 設(shè)計(jì)本質(zhì)
特性 | Arrays.ArrayList | java.util.ArrayList |
---|---|---|
存儲結(jié)構(gòu) | 包裝原始數(shù)組(final) | 動態(tài)數(shù)組(Object[] elementData) |
長度是否可變 | ? 固定長度 | ? 動態(tài)擴(kuò)容 |
是否支持增刪 | ? 拋出異常 | ? 正常操作 |
內(nèi)存占用 | 更低(直接引用原數(shù)組) | 更高(拷貝數(shù)據(jù)) |
關(guān)鍵限制:
- 底層數(shù)組由
final
修飾,無法擴(kuò)容 - 未重寫
add()
、remove()
等修改方法 - 繼承
AbstractList
的默認(rèn)實(shí)現(xiàn)(直接拋異常)
三、解決方案:創(chuàng)建真正的可變集合
3.1 使用 new ArrayList() 包裝(推薦)
String[] arr = {"Java", "Python", "Go"}; // 方案1:構(gòu)造方法包裝 List<String> mutableList = new ArrayList<>(Arrays.asList(arr)); // 方案2:Java 8+ Stream API List<String> mutableList = Arrays.stream(arr) .collect(Collectors.toList());
優(yōu)點(diǎn):代碼簡潔,兼容所有Java版本
3.2 Java 9+ 的 List.of() 替代方案
// 不可變集合(Java 9+) List<String> immutableList = List.of("Java", "Python", "Go"); // 需要可變時(shí)顯式轉(zhuǎn)換 List<String> mutableList = new ArrayList<>(immutableList);
注意:List.of()
創(chuàng)建的集合完全不可變(增刪改均拋異常)
3.3 特殊場景:修改原始數(shù)組
若只需修改元素值(不增刪元素),可操作原始數(shù)組:
String[] arr = {"Java", "Python", "Go"}; List<String> list = Arrays.asList(arr); // 修改元素(允許!) list.set(1, "C++"); System.out.println(Arrays.toString(arr)); // [Java, C++, Go] // 原始數(shù)組同步變化 arr[0] = "Rust"; System.out.println(list); // [Rust, C++, Go]
原理:集合直接引用原始數(shù)組,數(shù)據(jù)共享
四、最佳實(shí)踐與總結(jié)
4.1 使用場景決策樹
需要集合操作嗎? ├── 是 → 需要增刪元素? │ ├── 是 → 使用 new ArrayList<>(Arrays.asList(...)) │ └── 否 → 只需讀/改元素 → Arrays.asList() 或 List.of() └── 否 → 直接使用原始數(shù)組
4.2 各方案特性對比
方法 | 可變性 | 線程安全 | 內(nèi)存開銷 | Java版本要求 |
---|---|---|---|---|
Arrays.asList() | 部分? | 非安全 | 低 | 1.2+ |
new ArrayList<>(...) | ? | 非安全 | 中 | 1.2+ |
Arrays.stream().collect() | ? | 非安全 | 中 | 8+ |
List.of() | ? | 安全 | 低 | 9+ |
4.3 終極原則
明確需求:區(qū)分"只讀" vs "可變"場景
優(yōu)先新語法:Java 8+ 項(xiàng)目多用 Stream API
防御式編程:
// 返回不可修改視圖(避免誤操作) public List<String> getLanguages() { return Collections.unmodifiableList(Arrays.asList("Java", "Python")); }
到此這篇關(guān)于Java中Arrays.asList() 的不可變陷阱的文章就介紹到這了,更多相關(guān)Java Arrays.asList() 陷阱內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java SpringBoot+vue+實(shí)戰(zhàn)項(xiàng)目詳解
這篇文章主要介紹了SpringBoot+VUE實(shí)現(xiàn)前后端分離的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-09-09SpringBoot整合RabbitMQ實(shí)戰(zhàn)教程附死信交換機(jī)
這篇文章主要介紹了SpringBoot整合RabbitMQ實(shí)戰(zhàn)附加死信交換機(jī),本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06Java StringBuffer類與StringBuilder類用法實(shí)例小結(jié)
這篇文章主要介紹了Java StringBuffer類與StringBuilder類用法,結(jié)合實(shí)例形式總結(jié)分析了Java StringBuffer類與StringBuilder類的功能、原理及添加、刪除、替換、截取等操作實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-03-03JAVASE精密邏輯控制過程詳解(分支和循環(huán)語句)
在一個(gè)程序執(zhí)行的過程中各條語句的執(zhí)行順序?qū)Τ绦虻慕Y(jié)果是有直接影響的,這篇文章主要給大家介紹了關(guān)于JAVASE精密邏輯控制(分支和循環(huán)語句)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04@RequestParam?和@RequestBody注解的區(qū)別解析
在 Spring MVC 中,我們可以使用 @RequestParam 和 @RequestBody 來獲取請求參數(shù),但它們在用法和作用上有一些區(qū)別,這篇文章主要介紹了@RequestParam?和@RequestBody注解的區(qū)別,需要的朋友可以參考下2023-06-06Java詳細(xì)分析講解自動裝箱自動拆箱與Integer緩存的使用
裝箱就是把基本類型轉(zhuǎn)換成包裝類,拆箱就是把包裝類轉(zhuǎn)換成基本類型,下面這篇文章主要給大家介紹Java中自動裝箱、自動拆箱與Integer緩存,需要的朋友可以參考下2022-04-04