Java中Arrays.asList() 的不可变陷阱

 更新时间:2025年06月18日 09:15:08   作者:weixin_52318532  
本文主要介绍了Arrays.asList()创建的集合不可变原因,指出其底层数组为final且未重写修改方法,导致增删抛异常,下面就来介绍一下Arrays.asList() 陷阱,感兴趣的可以了解一下

一、问题现象:无法修改的集合

当开发者使用 Arrays.asList() 转换数组为集合时,尝试添加/删除元素会抛出异常:

String[] arr = {"Java", "Python", "Go"};  
List<String> list = Arrays.asList(arr);  

// 尝试添加元素  
list.add("JavaScript"); // 抛出 UnsupportedOperationException  

// 尝试删除元素  
list.remove(0); // 同样抛出异常  

控制台报错

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内部的私有静态类  
private static class ArrayList<E> extends AbstractList<E>  
    implements RandomAccess, java.io.Serializable {  
    
    private final E[] a; // final修饰的数组!  

    ArrayList(E[] array) {  
        a = Objects.requireNonNull(array);  
    }  

    // 未重写add/remove方法(继承AbstractList的默认实现)  
}  

// AbstractList.java  
public void add(int index, E element) {  
    throw new UnsupportedOperationException();  
}  

2.2 设计本质

特性Arrays.ArrayListjava.util.ArrayList
存储结构包装原始数组(final)动态数组(Object[] elementData)
长度是否可变❌ 固定长度✅ 动态扩容
是否支持增删❌ 抛出异常✅ 正常操作
内存占用更低(直接引用原数组)更高(拷贝数据)

关键限制

  • 底层数组由 final 修饰,无法扩容
  • 未重写 add()remove() 等修改方法
  • 继承 AbstractList 的默认实现(直接抛异常)

三、解决方案:创建真正的可变集合

3.1 使用 new ArrayList() 包装(推荐)

String[] arr = {"Java", "Python", "Go"};  

// 方案1:构造方法包装  
List<String> mutableList = new ArrayList<>(Arrays.asList(arr));  

// 方案2:Java 8+ Stream API  
List<String> mutableList = Arrays.stream(arr)  
        .collect(Collectors.toList());  

优点:代码简洁,兼容所有Java版本

3.2 Java 9+ 的 List.of() 替代方案

// 不可变集合(Java 9+)  
List<String> immutableList = List.of("Java", "Python", "Go");  

// 需要可变时显式转换  
List<String> mutableList = new ArrayList<>(immutableList);  

注意List.of() 创建的集合完全不可变(增删改均抛异常)

3.3 特殊场景:修改原始数组

若只需修改元素值(不增删元素),可操作原始数组:

String[] arr = {"Java", "Python", "Go"};  
List<String> list = Arrays.asList(arr);  

// 修改元素(允许!)  
list.set(1, "C++");  
System.out.println(Arrays.toString(arr)); // [Java, C++, Go]  

// 原始数组同步变化  
arr[0] = "Rust";  
System.out.println(list); // [Rust, C++, Go]  

原理:集合直接引用原始数组,数据共享

四、最佳实践与总结

4.1 使用场景决策树

需要集合操作吗?  
├── 是 → 需要增删元素?  
│   ├── 是 → 使用 new ArrayList<>(Arrays.asList(...))  
│   └── 否 → 只需读/改元素 → Arrays.asList() 或 List.of()  
└── 否 → 直接使用原始数组  

4.2 各方案特性对比

方法可变性线程安全内存开销Java版本要求
Arrays.asList()部分❌非安全1.2+
new ArrayList<>(...)非安全1.2+
Arrays.stream().collect()非安全8+
List.of()安全9+

4.3 终极原则

明确需求:区分"只读" vs "可变"场景

优先新语法:Java 8+ 项目多用 Stream API

防御式编程

// 返回不可修改视图(避免误操作)  
public List<String> getLanguages() {  
    return Collections.unmodifiableList(Arrays.asList("Java", "Python"));  
}  

到此这篇关于Java中Arrays.asList() 的不可变陷阱的文章就介绍到这了,更多相关Java Arrays.asList() 陷阱内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Java SpringBoot+vue+实战项目详解

    Java SpringBoot+vue+实战项目详解

    这篇文章主要介绍了SpringBoot+VUE实现前后端分离的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-09-09
  • SpringBoot整合RabbitMQ实战教程附死信交换机

    SpringBoot整合RabbitMQ实战教程附死信交换机

    这篇文章主要介绍了SpringBoot整合RabbitMQ实战附加死信交换机,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • Java StringBuffer类与StringBuilder类用法实例小结

    Java StringBuffer类与StringBuilder类用法实例小结

    这篇文章主要介绍了Java StringBuffer类与StringBuilder类用法,结合实例形式总结分析了Java StringBuffer类与StringBuilder类的功能、原理及添加、删除、替换、截取等操作实现技巧,需要的朋友可以参考下
    2019-03-03
  • JAVASE精密逻辑控制过程详解(分支和循环语句)

    JAVASE精密逻辑控制过程详解(分支和循环语句)

    在一个程序执行的过程中各条语句的执行顺序对程序的结果是有直接影响的,这篇文章主要给大家介绍了关于JAVASE精密逻辑控制(分支和循环语句)的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • @RequestParam 和@RequestBody注解的区别解析

    @RequestParam 和@RequestBody注解的区别解析

    在 Spring MVC 中,我们可以使用 @RequestParam 和 @RequestBody 来获取请求参数,但它们在用法和作用上有一些区别,这篇文章主要介绍了@RequestParam 和@RequestBody注解的区别,需要的朋友可以参考下
    2023-06-06
  • Java详细分析讲解自动装箱自动拆箱与Integer缓存的使用

    Java详细分析讲解自动装箱自动拆箱与Integer缓存的使用

    装箱就是把基本类型转换成包装类,拆箱就是把包装类转换成基本类型,下面这篇文章主要给大家介绍Java中自动装箱、自动拆箱与Integer缓存,需要的朋友可以参考下
    2022-04-04
  • Linux安装JDK两种方式详细教程(附图)

    Linux安装JDK两种方式详细教程(附图)

    这篇文章主要给大家介绍了关于Linux安装JDK两种方式详细教程的相关资料,Linux的使用相信大家都要用到java吧,在使用java前我们得先安装jdk以及配置环境变量等工作,需要的朋友可以参考下
    2023-11-11
  • Java接口测试Cookie与token原理解析

    Java接口测试Cookie与token原理解析

    这篇文章主要介绍了Java接口测试Cookie与token原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java基础之Maven详解

    Java基础之Maven详解

    这篇文章主要介绍了Java基础之Maven详解,文中有非常详细的代码示例,对正在学习java基础的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • Java排序算法总结之希尔排序

    Java排序算法总结之希尔排序

    这篇文章主要介绍了Java排序算法总结之希尔排序,较为详细的分析了希尔排序的原理与java的实现技巧,需要的朋友可以参考下
    2015-05-05

最新评论