数学集合中有交、并、差运算。java有一个完整的集合类,那么作为集合,他们支持交、并、差运算吗?答案是肯定的。

现场

业务中有个求List交集的运算,使用 java 官方的提供的 retainAll 方法居然报错UnsupportedOperationException
代码


    public static void main(String[] args) {
        List<Long> a = Arrays.asList(1L,2L,3L);
        List<Long> b = Arrays.asList(4L,3L,4L,6L);
        List<Long> c = Arrays.asList(5L,2L,3L);
        
        List<Long> res = Arrays.asList(3L,2L);
        List<List<Long>> d = Arrays.asList(a, b, c);

        a.retainAll(b);
        System.out.println(a);
        d.forEach(res::retainAll);
        System.out.println(res);
        
    }

运行后的错误

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.remove(AbstractList.java:161)
	at java.util.AbstractList$Itr.remove(AbstractList.java:374)
	at java.util.AbstractCollection.retainAll(AbstractCollection.java:410)
	at com.ghostcloud.jkwserver.components.router.AdjacencyListUtils.main(AdjacencyListUtils.java:349)

在这里插入图片描述
从截图很容易看出,代码编译没有报错,执行时报错,不科学呀。
网上list求交集demo

   public static void main(String[] args) {
		List<String> listA = new ArrayList<String>();
		List<String> listB = new ArrayList<String>();
		listA.add("A");
		listA.add("B");
		listB.add("B");
		listB.add("C");
		listA.retainAll(listB);
		System.out.println(listA);
	}

运行结果为:[B]
为什么我写的代码会报错呢?
对比代码,难道是创建List的方法不一样?
修改代码

解救

代码

    public static void main(String[] args) {
        List<Long> a = new ArrayList<>();
        Collections.addAll(a,1L,2L,3L);
        List<Long> b = new ArrayList<>();
        Collections.addAll(b,4L,3L,4L,6L);
        List<Long> c = new ArrayList<>();
        Collections.addAll(c,5L,2L,3L);

        List<Long> res = new ArrayList<>();
        Collections.addAll(res,3L,2L);
        List<List<Long>> d = new ArrayList<>();
        Collections.addAll(d,a, b, c);

        res.retainAll(b);
        System.out.println(a);
        d.forEach(res::retainAll);
        System.out.println(res);
    }

运行结果:
[1, 2, 3]
[3]
开发任务解决了,但是为什么呢?

分析

异常分析
为什么会发生 java.lang.UnsupportedOperationException 异常?
分析堆栈,异常最终是在at java.util.AbstractList.remove(AbstractList.java:161)
处抛出。
两种不同方式创建的List,调用 remove 方法,结果有差别,难道说创建的 List 有差异?接下来查看源码对比一下。

Arrays.asList()源码

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

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

        @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) {
        ...
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
    }

Arrays.asList() 使用的 ArrayListArrays 内部类,继承并实现了 AbstractList 中的一些方法。但是没有实现 remove方法,调用时当执行 AbstractListremove 方法,即我们看到的堆栈信息at java.util.AbstractList.remove(AbstractList.java:161)

AbstractListremove 方法

    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

AbstractListremove 方法直接抛出了这个错误。

ArrayListremove源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
...

}

结论

Arrays.asList()使用的是内部类ArrayList,交集运算 retainAll 最终调用其父类AbstractListretainAllremove方法去除非交集元素,直接抛出错误UnsupportedOperationException。而ArrayListremove 有自己具体的实现,不会调用 AbstractListremove方法,故不会报错。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐