欢迎光临
我们一直在努力

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

五年前,我在 JavaGuide 上就总结分享过 Arrays.asList() 把数组转换成集合存在的坑。

今天来分享一个 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 异常。

    具体原因

    内部类ArrayList的限制:

    • Arrays.asList(arr)返回的内部类ArrayList是一个固定长度的列表,它没有实现add和remove方法。

    • 当我们调用list.add(3)时,实际上调用的是AbstractList中的add方法,而这个方法直接抛出了UnsupportedOperationException异常。

    源码分析:

    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()的使用场景,确保代码的正确性和稳定性。同时,也应该从这次事故中吸取教训,加强代码审查和测试,避免类似问题的再次发生。

    赞(0)
    未经允许不得转载:171主机测评 » Arrays.asList() 数组转换成集合酿成的线上事故,差点要滚蛋了
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址