美文网首页
使用 Arrays.asList 把数据转换为 List 的三个

使用 Arrays.asList 把数据转换为 List 的三个

作者: xiaohei_e853 | 来源:发表于2022-08-29 16:33 被阅读0次

    Java 8 中 Stream 流式处理的各种功能,大大减少了集合类各种操作(投影、过滤、转换)的代码量。所以,在业务开发中,我们常常会把原始的数组转换为 List 类数据结构,来继续展开各种 Stream 操作。

    你可能也想到了,使用 Arrays.asList 方法可以把数组一键转换为 List,但其实没这么简单。接下来,就让我们看看其中的缘由,以及使用 Arrays.asList 把数组转换为 List 的几个坑。

    在如下代码中,我们初始化三个数字的 int[]数组,然后使用 Arrays.asList 把数组转换为 List:

    
    int[] arr = {1, 2, 3};
    List list = Arrays.asList(arr);
    log.info("list:{} size:{} class:{}", list, list.size(), list.get(0).getClass());
    

    但,这样初始化的 List 并不是我们期望的包含 3 个数字的 List。通过日志可以发现,这个 List 包含的其实是一个 int 数组,整个 List 的元素个数是 1,元素类型是整数数组。

    
    12:50:39.445 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - list:[[I@1c53fd30] size:1 class:class [I
    

    其原因是,只能是把 int 装箱为 Integer,不可能把 int 数组装箱为 Integer 数组。我们知道,Arrays.asList 方法传入的是一个泛型 T 类型可变参数,最终 int 数组整体作为了一个对象成为了泛型类型 T:

    
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
    

    直接遍历这样的 List 必然会出现 Bug,修复方式有两种,如果使用 Java8 以上版本可以使用 Arrays.stream 方法来转换,否则可以把 int 数组声明为包装类型 Integer 数组:

    
    int[] arr1 = {1, 2, 3};
    List list1 = Arrays.stream(arr1).boxed().collect(Collectors.toList());
    log.info("list:{} size:{} class:{}", list1, list1.size(), list1.get(0).getClass());
    
    
    Integer[] arr2 = {1, 2, 3};
    List list2 = Arrays.asList(arr2);
    log.info("list:{} size:{} class:{}", list2, list2.size(), list2.get(0).getClass());
    

    修复后的代码得到如下日志,可以看到 List 具有三个元素,元素类型是 Integer:

    
    13:10:57.373 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - list:[1, 2, 3] size:3 class:class java.lang.Integer
    

    可以看到第一个坑是,不能直接使用 Arrays.asList 来转换基本类型数组。那么,我们获得了正确的 List,是不是就可以像普通的 List 那样使用了呢?我们继续往下看。

    把三个字符串 1、2、3 构成的字符串数组,使用 Arrays.asList 转换为 List 后,将原始字符串数组的第二个字符修改为 4,然后为 List 增加一个字符串 5,最后数组和 List 会是怎样呢?

    
    String[] arr = {"1", "2", "3"};
    List list = Arrays.asList(arr);
    arr[1] = "4";
    try {
        list.add("5");
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    log.info("arr:{} list:{}", Arrays.toString(arr), list);
    

    可以看到,日志里有一个 UnsupportedOperationException,为 List 新增字符串 5 的操作失败了,而且把原始数组的第二个元素从 2 修改为 4 后,asList 获得的 List 中的第二个元素也被修改为 4 了:

    
    java.lang.UnsupportedOperationException
      at java.util.AbstractList.add(AbstractList.java:148)
      at java.util.AbstractList.add(AbstractList.java:108)
      at org.geekbang.time.commonmistakes.collection.aslist.AsListApplication.wrong2(AsListApplication.java:41)
      at org.geekbang.time.commonmistakes.collection.aslist.AsListApplication.main(AsListApplication.java:15)
    13:15:34.699 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - arr:[1, 4, 3] list:[1, 4, 3]
    

    这里,又引出了两个坑。

    第二个坑,Arrays.asList 返回的 List 不支持增删操作。Arrays.asList 返回的 List 并不是我们期望的 java.util.ArrayList,而是 Arrays 的内部类 ArrayList。ArrayList 内部类继承自 AbstractList 类,并没有覆写父类的 add 方法,而父类中 add 方法的实现,就是抛出 UnsupportedOperationException。相关源码如下所示:

    
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
    
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private final E[] a;
    
    
        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
    ...
    
        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }
        ...
    }
    
    public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    ...
    public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
    }
    

    第三个坑,对原始数组的修改会影响到我们获得的那个 List。看一下 ArrayList 的实现,可以发现 ArrayList 其实是直接使用了原始的数组。所以,我们要特别小心,把通过 Arrays.asList 获得的 List 交给其他方法处理,很容易因为共享了数组,相互修改产生 Bug。

    修复方式比较简单,重新 new 一个 ArrayList 初始化 Arrays.asList 返回的 List 即可:

    
    String[] arr = {"1", "2", "3"};
    List list = new ArrayList(Arrays.asList(arr));
    arr[1] = "4";
    try {
        list.add("5");
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    log.info("arr:{} list:{}", Arrays.toString(arr), list);
    

    修改后的代码实现了原始数组和 List 的“解耦”,不再相互影响。同时,因为操作的是真正的 ArrayList,add 也不再出错:

    
    13:34:50.829 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - arr:[1, 4, 3] list:[1, 2, 3, 5]
    

    相关文章

      网友评论

          本文标题:使用 Arrays.asList 把数据转换为 List 的三个

          本文链接:https://www.haomeiwen.com/subject/grfknrtx.html