前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ArrayList源码分析(基于jdk1.8)(三):Arrays.asList方法带来的问题

ArrayList源码分析(基于jdk1.8)(三):Arrays.asList方法带来的问题

作者头像
冬天里的懒猫
发布2020-08-11 14:49:47
4780
发布2020-08-11 14:49:47
举报

Arrays.asList,本来是另外一个类,之所以放到ArrayList相关的文章里面一并讨论,是因为这也是我们日常在使用过程中的一个误区,容易将Arrays.asList产生的结果与ArrayList进行等价。

1.问题重现

1.1 int数组转ArrayList问题

一开始,我们最简单的需求就是将一个数组转为list,搜索了很多资料之后,有人告诉你,Arrays.asList是专门解决这个问题的好办法:

代码语言:javascript
复制
public static void main(String[] args) {
	int[] arr = {1,2,3,4,5};
	List list = Arrays.asList(arr);
	System.out.println(list);
}

我们以为,结果会输出“1,2,3,4,5”但是这个结果确是:

代码语言:javascript
复制
[[I@1b6d3586]

这个结果让我们始料未及,究竟是哪出了问题呢?于是,对这个list中的内容进行debug:

在这里插入图片描述
在这里插入图片描述

可以发现,这个list中实际上只有一个内容,就是这个数组。asList并没有如我们想的将所有的数据的元素转换为ArrayList中的一项值。而是直接将数组变成了一个元素项。

1.2 asList之后的增删问题

对于上面章节,所提到的问题,如果不是单单纯的int数组,string数组是能够成功的。假定我们需要对这个string数组转为list之后,对这个ArrayList进行增删操作:

代码语言:javascript
复制
public static void main(String[] args) {
    String[] arr1 = {"1","2","3","4","5"};
	List list2 = Arrays.asList(arr1);
	System.out.println(list2);
	list2.add("6");
}

这又产生了一个意想不到的结果:

代码语言:javascript
复制
[1, 2, 3, 4, 5]
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at com.dhb.ArrayList.test.ArraysAsListTest.main(ArraysAsListTest.java:21)

出现了UnsupportedOperationException异常。

1.3 asList之后修改原数组的问题

如果我们在使用asList的时候,对原始数组进行修改,那么会出现什么结果?

代码语言:javascript
复制
public static void main(String[] args) {
    
    String[] arr1 = {"1","2","3","4","5"};
    List list2 = Arrays.asList(arr1);
    System.out.println(list2);
    arr1[1] = "0";
    System.out.println(list2);
}

输出为:

代码语言:javascript
复制
[1, 2, 3, 4, 5]
[1, 0, 3, 4, 5]

可以看到,当我们使用asList之后,再对其原始的数组进行修改,那么之前被转换的List的值也会发生变化。如果我们把这个结果通过参数传递给其他线程,那么可能就会产生很多共享数据导致的奇怪问题。

2.源码分析

2.1 asList方法

我们打开Arrays源码,看看asList方法:

代码语言:javascript
复制
  /**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with {@link Collection#toArray}.  The returned list is
     * serializable and implements {@link RandomAccess}.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

可以看到,asList方法实际上是一个泛型方法,支持传入的是泛型对象。前面我们讨论过泛型的擦除原理,泛型只支持对象。那么对于int数组,由于其元素都是int型,那么泛型是不支持的。那么只有数组才是对象,因此就不难理解为什么前面的int数组被转换成了一个只有一个元素的List,这个元素就是数组本身。 对于这个问题,更好的解决办法是,在jdk1.8之后出现了Stream的装箱的方法:

代码语言:javascript
复制
public static void main(String[] args) {
	int[] arr = {1,2,3,4,5};
	List list = Arrays.asList(arr);
	System.out.println(list);

	List list1 = Arrays.stream(arr).boxed().collect(Collectors.toList());
	System.out.println(list1);
}

输出结果:

代码语言:javascript
复制
[[I@1b6d3586]
[1, 2, 3, 4, 5]

这样通过Stream的bixed方法就很好的解决了这个问题。

2.2 ArrayList内部类

在Arrays的asList方法中,虽然new了一个ArrayList,但是需要注意的是,这个ArrayList并非我们一直使用的ArrayList,而是一个位于Arrays中的内部类:

代码语言:javascript
复制
   /**
     * @serial include
     */
    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);
        }
    }

通过这个源码我们可以发现,这个类继承了AbstractList,其所有的get和set操作都是对原有数组进行修改。而且没有add和remove等会修改数组大小的方法。

代码语言:javascript
复制
String[] arr1 = {"1","2","3","4","5"};
List list2 = Arrays.asList(arr1);
System.out.println(list2);
arr1[1] = "0";
System.out.println(list2);

list2.set(1,"7");
System.out.println(list2);
System.out.println(arr1[1]);

执行上述代码,可以看到输出为:

代码语言:javascript
复制
[1, 2, 3, 4, 5]
[1, 0, 3, 4, 5]
[1, 7, 3, 4, 5]
7

生成的ArrayList,实际上是原始数组的视图,这有点类似我们上一篇对ArrayList中subList方法的分析,都是一个视图功能的内部类。其get,set方法只要修改实际上是修改的原始数组,同理,修改原始数组,ArrayList的内容也会发生改变。这也不难理解为什么会出现UnsupportedOperationException异常。因为这个ArrayList本身并没有add和remove等方法。这个方法是通过抽象类实现的,而在抽象类中:

代码语言:javascript
复制
 /**
     * {@inheritDoc}
     *
     * <p>This implementation always throws an
     * {@code UnsupportedOperationException}.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     * @throws IllegalArgumentException      {@inheritDoc}
     * @throws IndexOutOfBoundsException     {@inheritDoc}
     */
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     *
     * <p>This implementation always throws an
     * {@code UnsupportedOperationException}.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws IndexOutOfBoundsException     {@inheritDoc}
     */
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

这两个方法直接调用本来就是会抛出UnsupportedOperationException异常。 所以这也进一步解释了,为什么ArrayList还需要用AbstractList再增加一层继承。这样可以很好的解耦。

2.3 **规范

对于上面这个问题,**巴巴的开发规范中也有描述:

在这里插入图片描述
在这里插入图片描述

禁止再对Arrays.asList产生的集合再进行add、remove、clear操作。这会导致UnsupportedOperationException异常。

3.总结

虽然此ArrayList并不是彼ArrayList,但是这也是我们容易混淆的地方,因此,对于Arrays.asList方法。我们需要注意的是:

  • 1.非对象数组,基本类型的数组不能使用Arrays.asList转换。如果需要使用,请用Stream中的boxed方法装箱之后再操作。
  • 2.asList产生的ArrayList实际上是Arrays的一个新的内部类。并不是我们认为的ArrayList,这也是大多数人容易混淆的地方。二者并不等价。
  • 3.asList产生的ArrayList实际上是对原始数组的一个视图,如果ArrayList进行set修改,那么原始数组的值也会改变。反之对原始数组进行修改,那么ArrayList的值也会改变。因此在我们将asList之后的结果再作为变量进行传递时,要特别注意这一点。
  • 4.Arrays.asList产生的ArrayList没有实现add、remove、clear方法,如果调用这些方法会出现UnsupportedOperationException异常。

上述是对Arrays.asList的总结,实际上这是很多人在编码的过程中,从来没有考虑过的问题。在面试过程中,实际上可能并不需要聊到HashMap,这ArrayList阶段就有很多人可能阵亡了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.问题重现
    • 1.1 int数组转ArrayList问题
      • 1.2 asList之后的增删问题
        • 1.3 asList之后修改原数组的问题
        • 2.源码分析
          • 2.1 asList方法
            • 2.2 ArrayList内部类
              • 2.3 **规范
              • 3.总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档