转载:Arrays.asList() 数组转换成集合酿成的线上事故,差点要滚蛋了!
引言
在Java开发中,数组与集合之间的转换是一个常见的操作。Arrays.asList()
方法因其简洁的语法和便捷的使用方式,被广泛应用于将数组转换为集合(如List)。
然而,这个看似简单的方法背后却隐藏着一个大坑,如果不小心处理,可能会导致线上事故,本文也是本人自身项目遇到的惨痛经历,希望能够通过讲解这个问题,让大家在实际工作中避开这个坑。
事故回顾
这个问题是出现开发一个电商平台的订单系统时,遇到了类似的问题。需要将一个订单ID数组转换为一个List,并在后续的业务逻辑中向这个List中添加新的订单ID。
由于个人经验不足,使用了Arrays.asList()
方法,部署到线上导致在添加新订单ID时抛出了UnsupportedOperationException
异常,整个订单处理流程中断,线上系统出现了严重的故障。
影响分析
这次事故对电商平台造成了巨大的影响:
- 用户体验下降: 由于订单处理流程中断,用户无法正常下单,导致用户体验大幅下降。
- 业务中断: 订单系统的故障直接影响到了整个电商平台的业务运营,造成了大量的订单积压。
- 经济损失: 由于业务中断,平台失去了大量的潜在收入,给公司带来了不小的经济损失。
- 信任危机: 频繁的系统故障让用户对平台的信任度下降,可能导致用户流失。
后面也是及时修复,吸取本次事故教训。
事故问题描述
首先先简单描述一下这个事故问题,废话不多说,直接看实际业务代码,也就将一个数组转换为List,并对这个List进行增删操作。
Integer[] arr = {1, 2}; List<Integer> list = Arrays.asList(arr); list.add(3);
上述代码看起来,没问题,编译器也没有报错。但是运行这段代码时,会抛出UnsupportedOperationException
异常,提示我们不支持添加操作。这究竟是怎么回事呢?

所以这是不是个大坑,如果在实际开发中没有进行自测,必定不会酿成重大线上事故。
问题分析
上述已经知道这个使用Arrays.asList()
将数组装换成List会在add情况会报错,接下来深入源码进行分析。
Arrays.asList()的内部实现
Arrays.asList(arr)
方法实际上返回的是一个Arrays类的内部类ArrayList
,而不是我们常用的java.util.ArrayList
。这个内部类ArrayList
继承自AbstractList
,并没有实现add和remove方法。


Arrays类的内部类ArrayList完整源码如下:
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private static final long serialVersionUID = -2764017481108945198L; private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } @Override public int size() { return a.length; } @Override public Object[] toArray() { return a.clone(); } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { int size = size(); if (a.length < size) return Arrays.copyOf(this.a, size, (Class<? extends T[]>) a.getClass()); System.arraycopy(this.a, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } @Override public E get(int index) { return a[index]; } @Override public E set(int index, E element) { E oldValue = a[index]; a[index] = element; return oldValue; } @Override public int indexOf(Object o) { E[] a = this.a; if (o == null) { for (int i = 0; i < a.length; i++) if (a[i] == null) return i; } else { for (int i = 0; i < a.length; i++) if (o.equals(a[i])) return i; } return -1; } @Override public boolean contains(Object o) { return indexOf(o) != -1; } @Override public Spliterator<E> spliterator() { return Spliterators.spliterator(a, Spliterator.ORDERED); } @Override public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); for (E e : a) { action.accept(e); } } @Override public void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); E[] a = this.a; for (int i = 0; i < a.length; i++) { a[i] = operator.apply(a[i]); } } @Override public void sort(Comparator<? super E> c) { Arrays.sort(a, c); } }
可以看到并没有实现add和remove方法,这两个方法在父类中,默认抛出UnsupportedOperationException
异常。

具体原因
下面是内部源码的分析
1.内部类ArrayList的限制:
Arrays.asList(arr)
返回的内部类ArrayList是一个固定长度的列表,它没有实现add和remove方法。- 当我们调用
list.add(3)
时,实际上调用的是AbstractList
中的add方法,而这个方法直接抛出了UnsupportedOperationException
异常。
2.源码分析:
- AbstractList的add方法如下:
public void add(int index, E element) { throw new UnsupportedOperationException(); }
- 这个方法没有具体实现,而是直接抛出异常。
对比java.util.ArrayList
类,很明显实现add和remove方法,并没有直接抛出UnsupportedOperationException
异常

解决方案
通过上述源码,为了避免这个问题,需要使用java.util.ArrayList
对Arrays.asList(arr)
返回的列表进行封装,而不是直接使用,这样就可以正常进行增删操作了,详细步骤如下;
详细步骤
在上述代码的基础上进行代码优化修改。
1.创建数组:
Integer[] arr = {1, 2};
2.转换为List:
List<Integer> list = Arrays.asList(arr);
3.封装为可变List:这个就是问题解决的关键,使用java.util.ArrayList
对Arrays.asList(arr)
进行封装
ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(arr));
4.进行增删操作:
arrayList.add(3); // 正常添加元素 arrayList.remove(1); // 正常删除元素
代码示例
最终完整代码如下:
public class Arrays_BugDemo { public static void main(String[] args) { Integer[] arr = {1, 2}; List<Integer> list = Arrays.asList(arr); // 使用ArrayList进行封装 ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(arr)); try { list.add(3); // 这里会报错 } catch (UnsupportedOperationException e) { System.out.println("list.add(3) 报错: " + e.getMessage()); } arrayList.add(3); // 现在可以正常添加元素 arrayList.forEach(System.out::println); } }
Java 中Arrays.asList()
返回不可变的List
,主要有以下几方面原因:
数据一致性与安全性
- 防止意外修改:
Arrays.asList()
返回的List
是基于原始数组创建的视图,其目的是为了方便以List
的形式操作数组内容。如果允许对这个List
进行添加、删除等结构修改操作,就会导致原始数组的长度和内容发生变化,这可能会违背数组本身作为固定长度数据结构的设计初衷,也容易在程序中引入难以排查的错误。例如,在多线程环境下,多个线程可能同时访问这个List
和原始数组,如果List
可随意修改,可能导致数据不一致问题。 - 保护原始数据:在很多场景下,我们希望将数组转换为
List
后,只是用于查询、遍历等操作,并不希望原始数据被意外篡改。返回不可变的List
可以确保原始数组的数据安全性,让开发者更放心地使用转换后的List
进行相关操作。
设计意图与功能定位
- 轻量级视图:
Arrays.asList()
主要是提供一种将数组转换为List
视图的方式,方便在需要List
接口的地方使用数组数据,而不是创建一个功能完备、可随意修改的List
集合。它更像是一个 “包装”,让数组能够适配List
相关的操作,如使用for - each
循环遍历、调用List
的一些查询方法等,而不是为了进行复杂的集合结构修改。 - 与其他不可变集合的一致性:Java 中存在一些不可变集合类,如
Collections.unmodifiableList()
返回的也是不可变的List
。Arrays.asList()
返回不可变List
符合 Java 集合框架中对于不可变集合的设计理念,使开发者在处理不可变数据结构时有统一的编程模型和使用体验。
性能与实现成本
- 性能考虑:实现可变集合需要额外的机制来处理元素的添加、删除等操作,比如动态调整数组大小、维护元素顺序等。而返回不可变的
List
可以避免这些复杂的操作和额外的开销,在性能上更优。因为它不需要处理元素插入、删除时可能涉及的数组扩容、数据迁移等操作,对于仅需读取数据的场景,能提高运行效率。 - 简化实现:从实现角度看,返回不可变的
List
实现起来相对简单。Arrays.asList()
返回的List
实际上是Arrays
类的一个内部类,这个内部类直接引用原始数组,没有提供add
、remove
等修改结构的方法(调用这些方法会抛出UnsupportedOperationException
异常),这样的设计简化了代码实现,减少了出错的可能性。