⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7411047482653196303 「小明爱吃火锅」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

引言

在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);
}
}

运行结果,程序可以正常执行,并且最终arrayList可以添加新元素。

总结

通过本文的详细讲解,我们可以得出以下几点结论:

  • Arrays.asList(arr)返回的是一个固定长度的List集合,没有add和remove具体实现方法,不能进行增删操作,否则会报错UnsupportedException。
  • 如果需要进行增删操作,可以使用java.util.ArrayList进行封装,如:new ArrayList<>(Arrays.asList(arr))。

希望本文能够帮助大家避免类似的线上事故,提高代码的健壮性和可靠性。在实际开发中,务必注意Arrays.asList()的使用场景,确保代码的正确性和稳定性。同时,也应该从这次事故中吸取教训,加强代码审查和测试,避免类似问题的再次发生。

文章目录
  1. 1. 引言
  2. 2. 事故回顾
    1. 2.0.1. 影响分析
  • 3. 事故问题描述
  • 4. 问题分析
    1. 4.0.1. Arrays.asList()的内部实现
    2. 4.0.2. 具体原因
  • 5. 解决方案
    1. 5.0.1. 详细步骤
    2. 5.0.2. 代码示例
  • 6. 总结