5

我使用 subList 方法创建了一个新的 ArrayList。现在,当我尝试使用 retainAll 执行交集操作时,它会抛出以下异常

retainAll() 方法适用于以下代码

List<Integer> arrNums1 = new ArrayList<Integer>();
arrNums1.add(1);
arrNums1.add(2);
arrNums1.add(3);

List<Integer> arrNums2 = arrNums1.subList(0, 1);
arrNums2.retainAll(arrNums1);

但是当我尝试为下面的代码应用retainAll时,它会生成如下异常

Java 代码

public class Generics1
{   
 public static void main(String[] args)
 {
       List<Fruits> arrFruits = new ArrayList<Fruits>();

        Fruits objApple  = new Apple();
        Fruits objOrange = new Orange();
        Fruits objMango  = new Mango();

        arrFruits.add(objApple);
        arrFruits.add(objOrange);
        arrFruits.add(objMango);

        List<Fruits> arrNewFruits = arrFruits.subList(0, 1);

        System.out.println(arrFruits.retainAll(arrNewFruits));
  }
}

class Fruits {}

class Apple extends Fruits {}

class Orange extends Fruits {}

class Mango extends Fruits {}

错误

在此处输入图像描述

4

3 回答 3

8

当您使用List#subList()时:

返回此列表在指定的 fromIndex(包括)和 toIndex(不包括)之间的部分的视图。(如果 fromIndex 和 toIndex 相等,则返回列表为空。)返回列表由此列表支持,因此返回列表中的非结构性更改会反映在此列表中,反之亦然。返回的列表支持此列表支持的所有可选列表操作。

您可以更改其中的元素,但不能更改列表的结构。

该文档进一步说:

如果后备列表(即此列表)以除通过返回列表之外的任何方式进行结构修改,则此方法返回的列表的语义将变为未定义。(结构修改是改变这个列表的大小,或者以其他方式扰乱它,使得正在进行的迭代可能会产生不正确的结果。)

retainAll()函数使用迭代器来删除不相交的值,这会导致ConcurrentModificationException。请注意文档中的内容:

请注意,此异常并不总是表示对象已被不同的线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。

制作副本List然后执行retainAll()

List<Fruits> arrNewFruits = new ArrayList<>(arrFruits.subList(0, 1));
于 2013-07-10T07:09:32.120 回答
3

在您的两个代码示例中,您有相反顺序的大列表和子列表。

当您retainAll()在子列表上调用时,不会发生任何修改。

这是因为子列表中的每个元素都在大列表中。

如果没有修改发生,ConcurrentModificationException则不会抛出 no。

您在上面使用Integers列表执行此操作。


如果您颠倒顺序并retainAll()在大列表上调用,它将发生变异。

这是因为并非大列表中的每个项目都在子列表中。

当您从大列表中删除一个元素时,ConcurrentModificationException会抛出 a。

这是因为您不能在迭代列表时对其进行变异

您在上面使用Fruits列表执行此操作。


迭代发生在retainAll()方法中。

在您的代码中, list 参数恰好引用了正在修改的同一个列表。

这是因为工作方式List.subList()

返回此列表在指定的 fromIndex(包括)和 toIndex(不包括)之间的部分的视图。(如果 fromIndex 和 toIndex 相等,则返回列表为空。)返回列表由此列表支持,因此返回列表中的非结构性更改会反映在此列表中,反之亦然。


长话短说:

如果您将代码更改为以下内容,则不会出现异常:

System.out.println(arrNewFruits.retainAll(arrFruits));

更重要的是:

如果在迭代其中一个列表时有可能修改任一列表,则需要从子列表创建一个新列表。

您可以像这样从子列表创建一个新列表:

List<Foo> freshList = new ArrayList<Foo>(bigList.subList(0,2));

现在您可以随心所欲地迭代和变异!


这是 的实现ArrayList.retainAll(),您可以在其中查找迭代。

于 2013-07-10T07:31:09.203 回答
2

问题是这arrNewFruits实际上只是一部分的逻辑视图arrFruits1为避免该错误,需要制作一个独立的列表:

List<Fruits> arrNewFruits = new ArrayList<>(arrFruits.subList(0, 1));

1这就是为什么您可以通过调用clear()a 来删除列表的一部分——对一个的subList()更改可以在另一个中看到。

于 2013-07-10T07:13:15.180 回答