【踩坑系列】Arrays$asList()抛UnsupportedOperationException异常

今天写代码时遇到一个很奇怪的问题,我很主观的利用Arrays,asList()方法返回一个List,然后对该List进行了add()方法的调用,结果竟然抛了异常。

示例代码:

@org.junit.Test
public void test(){
    List<String> list = Arrays.asList("aa","bb");
    boolean cc = list.add("cc");

    System.out.println(list);
}

报错如下:

java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at com.chuangkit.design.center.assist.impl.Test.test(Test.java:19)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
1.接下来我们看下源码,排查异常产生的原因

我们点进asList()方法看下源码是怎么实现的。

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

这里发现这不是返回的就是一个new出来的ArrayList集合对象吗,没有什么问题啊,别着急,我们在进到ArrayList看看猫腻。

/**
     * @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);
        }
    }

这时我们发现,此ArrayList并不是我们常用的java.util包下的,而是Arrays类自己实现的一个静态内部类,该内部类继承了AbstractList抽象类,我们知道AbstractList是实现了List接口的,而AbstractList本身也是一个抽象类,并没有对add等方法进行实现,Arrays$ArrayList虽然继承了AbstractList,但是也没有对add等方法进行实现,所以当我们调用该内部类的add方法时就抛出UnsupportedOperationException异常。

// 1.首先我们看ArrayList继承了AbstractList
private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {

// 2.然后AbstractList又实现了List接口
public abstract class AbstractList<E> extends AbstractCollection<E> 
		implements List<E> 
	{

// 3.在List的add方法注释上可以看到如果实现类没有实现add方法就会抛异常
/**
  * ...
  * @throws UnsupportedOperationException if the {@code add} operation
  *         is not supported by this list
  * ...
  */
public boolean add(E e) {
    add(size(), e);
    return true;
}
2.问题我们找到了,接下来我们想一想为什么Arrays要自己实现一个ArrayList呢

我们先看下源码注释是怎么说的(摘自于asList方法注释)

/**
  * 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:
  * ...
  * 返回由指定数组支持的固定大小列表。(更改返回的列表“直写”到数组。)
  * 此方法与 {@link Collection#toArray} 结合,充当基于数组和基于集合的 API 之间的桥梁。
  * 返回的列表是可序列化的并实现了 {@link RandomAccess}。此方法还提供了一种创建固定大小列表
  * 的便捷方法,该列表已初始化为包含多个元素。
  * ...
  * @return a list view of the specified array
  * 指定数组的列表视图
  */
  • asList方法返回的是当前数组的的列表视图,当我们改变原始数组的数据时,这个"视图"会自然而然的同步改变,因为它就是当前数组的一个展现形式,如果返回的是java.util包下的ArrayList,无疑会创建一个新的数组来代替当前数组,并且我们新的数据进行修改,原数组的数据也不会发生改变。
  • asList方法返回的是一个固定大小列表,也就是说只要数组创建了,我们无法向数组中添加新的元素,只能对现有元素进行替换和查找,并且不能删除元素。这个java.util包下的ArrayList也是无法做到的。
  • 此方法充当基于数组的API和基于集合的API之间的桥梁,这句话也体现了,asList方法本身就不是一个真正意义上的集合方法,只是在基于两者的API基础上做了一个衔接。
3.总结

Arrays$asList()本身并没有创建和返回一个新的集合对象,返回的仍然是描述原数组的视图,也就是该"集合"只满足对数组的操作,一但声明集合大小不能修改、不能直接删除某个元素等等;也就是说当我们通过asList方法将数组转成集合之后,我们对该集合的操作仅仅满足于对一个数组的操作,那么我们是可以使用这个方法的,如果想要得到的集合满足于一个真实集合对象的使用,那么这个方法是不适用的,我们这时候需要将数组复制到一个新的数组支持集合的创建,比如

@org.junit.Test
public void test(){
    List<String> list = Arrays.asList("aa","bb");

    List<String> temp = new ArrayList<>(list);
    temp.add("cc");

    System.out.println(temp);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值