Arrays.asList() 数组转换成集合酿成的线上事故,差点要滚蛋了!

简介: 本文介绍了Java开发中使用`Arrays.asList()`方法将数组转换为集合时的一个常见陷阱。该方法返回的List是固定长度的,不支持添加或删除操作,直接使用可能导致线上故障。文章通过一次实际开发中的事故案例,分析了问题的原因,并提供了使用`java.util.ArrayList`进行封装的解决方案,以避免此类错误的发生。希望读者能从中吸取教训,提高代码的健壮性。

引言

在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.ArrayListArrays.asList(arr)返回的列表进行封装,而不是直接使用,这样就可以正常进行增删操作了,详细步骤如下;

详细步骤

在上述代码的基础上进行代码优化修改。

1.创建数组:

Integer[] arr = {1, 2};

2.转换为List:

List<Integer> list = Arrays.asList(arr);

3.封装为可变List:这个就是问题解决的关键,使用java.util.ArrayListArrays.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()的使用场景,确保代码的正确性和稳定性。同时,也应该从这次事故中吸取教训,加强代码审查和测试,避免类似问题的再次发生。

相关文章
|
3月前
|
Java API 开发者
代码小妙招:用Java轻松获取List交集数据
在Java中获取两个 `List`的交集可以通过 `retainAll`方法和Java 8引入的流操作来实现。使用 `retainAll`方法更为直接,但会修改原始 `List`的内容。而使用流则提供了不修改原始 `List`、更为灵活的处理方式。开发者可以根据具体的需求和场景,选择最适合的方法来实现。了解和掌握这些方法,能够帮助开发者在实际开发中更高效地处理集合相关的问题。
99 1
|
6月前
|
Java 索引
那些年,我们追过的Java List——ArrayList与LinkedList的爱恨情仇
【6月更文挑战第17天】ArrayList与LinkedList,Java List接口的双子星,各有千秋。ArrayList基于数组,随机访问快速,但插入删除慢;LinkedList用链表实现,插入删除高效,但索引访问慢。两者在爱恨情仇中教会我们权衡选择,成为编程旅程中难忘的记忆。 ```
27 0
|
6月前
|
Java
JavaSE——集合框架二(2/6)-综合案例-斗地主游戏(做牌、洗牌、发牌、排序、看牌)
JavaSE——集合框架二(2/6)-综合案例-斗地主游戏(做牌、洗牌、发牌、排序、看牌)
26 0
|
算法 C++
[C++随笔录] list使用
[C++随笔录] list使用
|
安全 小程序 Java
带了一个 3 年的开发,不会循环删除 List 中的元素,心态崩了。。(2)
带了一个 3 年的开发,不会循环删除 List 中的元素,心态崩了。。
带了一个 3 年的开发,不会循环删除 List 中的元素,心态崩了。。(2)
|
安全 小程序 Java
带了一个 3 年的开发,不会循环删除 List 中的元素,心态崩了。。(1)
带了一个 3 年的开发,不会循环删除 List 中的元素,心态崩了。。
带了一个 3 年的开发,不会循环删除 List 中的元素,心态崩了。。(1)
|
存储 安全 算法
《我要进大厂》- Java集合夺命连环13问,你能坚持到第几问?(Map | Collections)
《我要进大厂》- Java集合夺命连环13问,你能坚持到第几问?(Map | Collections)
《我要进大厂》- Java集合夺命连环13问,你能坚持到第几问?(Map | Collections)
|
存储 安全 算法
《我要进大厂》- Java集合夺命连环14问,你能坚持到第几问?(集合概述 | List | Set | Queue)
《我要进大厂》- Java集合夺命连环14问,你能坚持到第几问?(集合概述 | List | Set | Queue)
《我要进大厂》- Java集合夺命连环14问,你能坚持到第几问?(集合概述 | List | Set | Queue)
|
存储 安全 Java
java集合类史上最细讲解 - List篇
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
117 0
java集合类史上最细讲解 - List篇
|
前端开发
前端工作总结135-map形成数组
前端工作总结135-map形成数组
75 0
前端工作总结135-map形成数组