子列表引发的ConcurrentModificationException异常

24

我有一个非常简单的代码:

    List<String> list = new ArrayList<String>();
    String a = "a";
    String b = "b";
    String c = "c";
    String d = "d";

    list.add(a);
    list.add(b);
    list.add(c);

    List<String> backedList = list.subList(0, 2);
    list.add(0, d); 
    System.out.println("2b: " + backedList);
我执行list.add(0, d)时出现ConcurrentModificationException异常。通常情况下,这是由sublist()引起的。我很困惑,因为针对sublist()的文档说:

返回的列表由此列表支持,因此在返回的列表中进行非结构性更改会反映在此列表中,反之亦然。

您可以解释一下问题出在哪里吗?


3
“问题”在于向列表中添加一个元素是一种结构性的改变。 - Stephen C
该句中的“反之”是误导性的,因为正如文档(以及您的代码)后面所解释的那样,在不通过子列表修改后端列表将会引发ConcurrentModificationException异常。 - David Santiago Turiño
4个回答

21

subList 是原始列表的一个视图(详见此处)。您可以对其中的元素进行修改,但不能更改列表的结构。

根据文档,如果您试图进行结构性更改,则 subList 的行为是不确定的。我猜在这个特定的实现中,将 ConcurrentModificationException 作为未定义行为。

如果在除返回列表以外的任何方式(例如更改此列表的大小或以某种方式干扰它,以致正在进行的迭代可能产生不正确的结果)修改支持列表(即此列表),则通过此方法返回的列表的语义变得不确定。


我浪费了一天的时间来检查到底出了什么问题,谢谢这个帮助我重新排列代码行的方法。 - Akshay Joshi
请查看我下面添加的答案,以更清楚地解释上面的评论。 - Akshay Joshi

7
返回结果:

返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。 参考链接

上述陈述是绝对正确的,但我们必须注意非结构性更改。我想举两个例子来证明以上声明。
示例1:列表中执行非结构性更改。

public static void main(String[] args) {
        List<String> listArr = new ArrayList<>();
        listArr.add("Delhi");
        listArr.add("Bangalore");
        listArr.add("New York");
        listArr.add("London");

        List<String> listArrSub = listArr.subList(1, 3);

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);

        //Performing Non-Structural Change in list.
        Collections.swap(listArr, 0, 1);

        System.out.println("\nAfter Non-Structural Change...\n");

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);
    }

输出结果-:

List-: [Delhi, Bangalore, New York, London]
Sub List-: [Bangalore, New York]

After Non-Structural Change...

List-: [Bangalore, Delhi, New York, London]
Sub List-: [Delhi, New York]

根据Oracle的文档说明,交换操作在两个列表中都会反映。
示例2:在子列表中执行非结构性更改。
public static void main(String[] args) {
        List<String> listArr = new ArrayList<>();
        listArr.add("Delhi");
        listArr.add("Bangalore");
        listArr.add("New York");
        listArr.add("London");

        List<String> listArrSub = listArr.subList(1, 3);

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);

        //Performing Non-Structural Change in sub list.
        Collections.swap(listArrSub, 0, 1);

        System.out.println("\nAfter Non-Structural Change...\n");

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);
    }

输出-:

List-: [Delhi, Bangalore, New York, London]
Sub List-: [Bangalore, New York]

After Non-Structural Change...

List-: [Delhi, New York, Bangalore, London]
Sub List-: [New York, Bangalore]

根据Oracle的文档,交换操作反映在两个列表中,但是它是在子列表上执行的。
我们已经看到了上述两个示例中的非结构性更改。现在让我们来看看结构性更改,如下所述,在Oracle的文档中给出。
如果通过返回的列表以外的任何方式对支持列表(即该列表)进行结构修改,则此方法返回的列表的语义将变得不确定。(结构修改是指改变该列表的大小或以其他方式扰动它,以使正在进行的迭代可能产生不正确的结果。)
示例3:在列表中执行结构性更改。
 public static void main(String[] args) {
        List<String> listArr = new ArrayList<>();
        listArr.add("Delhi");
        listArr.add("Bangalore");
        listArr.add("New York");
        listArr.add("London");

        List<String> listArrSub = listArr.subList(1, 3);

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);

        //Performing Structural Change in list.
        listArr.add("Mumbai");

        System.out.println("\nAfter Structural Change...\n");

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);
    }

输出-:

List-: [Delhi, Bangalore, New York, London]
Sub List-: [Bangalore, New York]

After Structural Change...

List-: [Delhi, Bangalore, New York, London, Mumbai]
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
    at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)
    at java.util.AbstractList.listIterator(AbstractList.java:299)
    at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)
    at java.util.AbstractCollection.toString(AbstractCollection.java:454)
    at java.lang.String.valueOf(String.java:2982)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at infosys.Research.main(Research.java:26)

根据Oracle文档所述,结构修改操作在除通过返回的列表之外以任何方式对后备列表(即此列表)进行结构修改时,将抛出java.util.ConcurrentModificationException异常。此方法返回的列表语义如果未通过返回的列表进行修改,则变为未定义状态。
示例4:在子列表中进行结构更改。
public static void main(String[] args) {
        List<String> listArr = new ArrayList<>();
        listArr.add("Delhi");
        listArr.add("Bangalore");
        listArr.add("New York");
        listArr.add("London");

        List<String> listArrSub = listArr.subList(1, 3);

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);

        //Performing Structural Change in sub list.
        listArrSub.add("Mumbai");

        System.out.println("\nAfter Structural Change...\n");

        System.out.println("List-: " + listArr);
        System.out.println("Sub List-: " + listArrSub);
    }

输出-:

List-: [Delhi, Bangalore, New York, London]
Sub List-: [Bangalore, New York]

After Structural Change...

List-: [Delhi, Bangalore, New York, Mumbai, London]
Sub List-: [Bangalore, New York, Mumbai]
结构修改 对返回的列表进行的修改已经有效地反映在整个列表中。

0

list.add(0, d) 涉及将所有项目移动一个位置并增加列表的大小。这是相当结构性的更改。


0

遇到此错误的情况

  • 我有一个包含100个项目的列表(原始列表)
  • 将原始列表按升序排序
  • 对其进行子列表化 -> 创建了一个升序子列表
  • 将原始列表按降序排序
  • 迭代子列表(升序子列表)列表

出现并发修改异常

以上场景的解决方法

  • 我有一个包含100个项目的列表(原始列表)
  • 将其按升序排序
  • 对其进行子列表化 -> 创建了一个升序子列表
  • 迭代子列表(升序子列表)列表
  • 将原始列表按降序排序

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接