求两个List是否存在交集 retainAll()方法报 java.lang.UnsupportedOperationException 异常

一.背景

工作中遇到一个很简单的需求,求两个list中是否存在相同的元素,即求两个list是否存在交集,例如listA = [1, 2, 3],listB = [2],则listA 和 listB 存在交集 [2]

二.错误示例

public class Test {

    public static void main(String[] args) {
        List<Integer> listA = Arrays.asList(1, 2, 3);
        List<Integer> listB = new ArrayList<>();
        listB.add(2);
        // retain是保留的意思,listA与listB做交集,结果集赋值给listA,如果listA被改变返回true,否则返回false
        System.out.println(listA.retainAll(listB));
        // 期望返回 [2]
        System.out.println(listA);
    }

}

大家可以看下上面的代码是否存在问题?

我们期望list = [2],但是当我们执行代码的时候,会报 java.lang.UnsupportedOperationException 异常

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 Test.main(Test.java:16)

有意思的是,当我们调整listA 和 listB 中的元素,listA = [1],listB = [1, 2, 3],就不会报错,至于原因会在源码解析中讲解。

public class Test {

    public static void main(String[] args) {
//        List<Integer> listA = Arrays.asList(1, 2, 3);
//        List<Integer> listB = new ArrayList<>();
//        listB.add(2);
//        // retain是保留的意思,listA与listB做交集,结果集赋值给listA,如果listA被改变返回true,否则返回false
//        System.out.println(listA.retainAll(listB));
//        // 期望返回 [2]
//        System.out.println(listA);

        List<Integer> listA = Arrays.asList(1);
        List<Integer> listB = new ArrayList<>();
        listB.add(1);
        listB.add(2);
        listB.add(3);
        System.out.println(listA.retainAll(listB));
        // 期望返回 [1]
        System.out.println(listA);
    }

}

三.正确示例

public class Test {

    public static void main(String[] args) {
        List<Integer> listA = new ArrayList<>();
        listA.add(1);
        listA.add(2);
        listA.add(3);
        List<Integer> listB = new ArrayList<>();
        listB.add(2);
        // retain是保留的意思,listA与listB做交集,结果集赋值给listA,如果listA被改变返回true,否则返回false
        System.out.println(listA.retainAll(listB));
        // 期望返回 [2]
        System.out.println(listA);
    }

}

此时运行代码可以正确返回

true
[2]

四.源码解析

retainAll() 是List接口的一个方法,它有两个实现,一个是 ArrayList,一个是 AbstractCollection,如果使用 ArrayList 的实现,
是不存出现本文所述的异常。

我来看一下AbstractCollection中的源码

public abstract class AbstractCollection<E> implements Collection<E> {
    /**
     * {@inheritDoc}
     *
     * <p>This implementation iterates over this collection, checking each
     * element returned by the iterator in turn to see if it's contained
     * in the specified collection.  If it's not so contained, it's removed
     * from this collection with the iterator's <tt>remove</tt> method.
     *
     * <p>Note that this implementation will throw an
     * <tt>UnsupportedOperationException</tt> if the iterator returned by the
     * <tt>iterator</tt> method does not implement the <tt>remove</tt> method
     * and this collection contains one or more elements not present in the
     * specified collection.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     *
     * @see #remove(Object)
     * @see #contains(Object)
     */
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }
}

源码大致思路就是将listA中存在,listB中不存在的元素,在listA中remove掉,问题就出在这个remove上,
由于此处返回的迭代器没有实现remove方法,默认就会返回 UnsupportedOperationException 异常

default void remove() {
    throw new UnsupportedOperationException("remove");
}

其实通过阅读注释也可以发现,作者已经将此问题出现的原因解释清楚了

Note that this implementation will throw an
UnsupportedOperationException if the iterator returned by the
iterator method does not implement the remove method
and this collection contains one or more elements not present in the
specified collection.

像上文中提到的为什么listA = [1],listB = [1, 2, 3],就不会报错?原因就是只有在listA中存在的元素,在listB中不存在,才会调用remove方法,
当前举的例子,listA中的元素,listB中也存在,就不会调用remove方法,也就不会报错

五.总结

看似一个简单的方法,使用起来还是有很多地方是需要注意的。因此我们在使用java类库中的一些源码时,阅读源码注释是一个很好的习惯。

Logo

鸿蒙生态一站式服务平台。

更多推荐