ConcurrentModificationException:.add()vs .addAll()

时间:2014-10-03 18:20:27

标签: java foreach concurrentmodification

为什么会发生以下情况?两者都不应该起作用吗?

List<String> items = data;
for( String id : items ) {
    List<String> otherItems = otherData;        

    // 1.   addAll()
    //Causes ConcurrentModificationException
    items.addAll(otherItems);

    // 2.   .add()
    //Doesn't cause exceptions
    for( String otherId : otherItems ) {
        items.add(otherId);
    }
}

是因为add()添加了集合Items,但addAll()创建了一个新集合,因此将Items修改为List的另一个实例?

修改 itemsotherItems具体类型ArrayList<String>

3 个答案:

答案 0 :(得分:6)

两种操作都不正确,因为它在迭代时修改了集合。

检查the implementation of ArrayList表明调用addaddAll应该在下一次循环迭代时成功抛出ConcurrentModificationException。对于add它没有这样做的事实意味着,对于您拥有的特定Java版本,ArrayList类中存在一个模糊的错误;或者(更有可能)otherItems是空的,所以在第二种情况下,你实际上并没有真正打电话给add

我确定otherItems必须为空,因为如果添加到Items列表&#34;工作&#34;以你想要的方式,它会在循环中每次都会增长,导致它无限循环,直到死于OutOfMemoryError。

答案 1 :(得分:4)

你在这里的for循环

for( String id : Items ) {

在逻辑上与:

相同
for(Iterator<String> it = Items.iterator(); it.hasNext();) {
    String id = it.next();

    ....
}

现在,如果修改列表,迭代器会迭代,在迭代过程中,会得到ConcurrentModificationException。来自Javadoc for ArrayList

  

此类的iterator和listIterator方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时候对列表进行结构修改,除非通过迭代器自己的删除或者添加方法,迭代器将抛出一个ConcurrentModificationException。

因此,addAll()正在修改Items,导致迭代器失败。

您唯一的解决方案是:

  1. 不要做addAll()
  2. 使用迭代器切换到循环,并执行:

    for (String other : otherItems) {
        it.add(other);
    }
    

    换句话说,通过迭代器进行添加,并避免使用ConcurrentModification ....

  3. 现在,为什么add()有效,addAll()没有?我相信你可能只是在迭代器中没有其他项目时看到类似添加版本的东西,或者添加的值是emtpry,也许是项目实现中的一个错误。 应该投掷一个CME,并且它不会抛出一个CME,这意味着存在一个错误,不是在您的代码中,而是在集合中。

    他们应该 失败!

    但是:您随后发现addAll()正在添加一个空集合。一个空的addAll()不应该导致CME ...并且,正如@Boann所指出的,这是ArrayList实现中的一个错误。

    我已经整理了以下测试以证明事情:

    private static List<String> buildData() {
        return new ArrayList<>(Arrays.asList("Hello", "World"));
    }
    
    public static void testThings(List<String> data, List<String> addall, List<String> add) {
        System.out.printf("Using %s addAll %s and add %s%n", data, addall, add);
    
        try {
            for (String s : data) {
                if (addall != null) {
                    data.addAll(addall);
                }
                if (add != null) {
                    for (String a : add) {
                        data.add(a);
                    }
                }
            }
            System.out.println("OK: " + data);
        } catch (Exception e) {
            System.out.println("Fail: " + e.getClass() + " -> " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
    
        String[] hw = {"Hello", "World"};
    
        testThings(buildData(), Arrays.asList(hw), null);
        testThings(buildData(), null, Arrays.asList(hw));
        testThings(new ArrayList<>(), Arrays.asList(hw), null);
        testThings(new ArrayList<>(), null, Arrays.asList(hw));
        testThings(buildData(), new ArrayList<>(), null);
        testThings(buildData(), null, new ArrayList<>());
        testThings(new ArrayList<>(), new ArrayList<>(), null);
        testThings(new ArrayList<>(), null, new ArrayList<>());
    }
    

    这会产生结果:

    Using [Hello, World] addAll [Hello, World] and add null
    Fail: class java.util.ConcurrentModificationException -> null
    Using [Hello, World] addAll null and add [Hello, World]
    Fail: class java.util.ConcurrentModificationException -> null
    Using [] addAll [Hello, World] and add null
    OK: []
    Using [] addAll null and add [Hello, World]
    OK: []
    Using [Hello, World] addAll [] and add null
    Fail: class java.util.ConcurrentModificationException -> null
    Using [Hello, World] addAll null and add []
    OK: [Hello, World]
    Using [] addAll [] and add null
    OK: []
    Using [] addAll null and add []
    OK: []
    

    注意两行:

    Using [Hello, World] addAll [] and add null
    Fail: class java.util.ConcurrentModificationException -> null
    Using [Hello, World] addAll null and add []
    OK: [Hello, World]
    

    添加空的addAll会导致CME,但这不会在结构上修改列表。这是ArrayList中的一个错误。

答案 2 :(得分:0)

我同意rolfl - &gt;在两种情况下都应该抛出CME ......

最可能的答案:

addAll()是第一次调用,它抛出异常,第二段代码根本就没有到达 - &gt;你忘了评论第一部分,因此认为add() - 部分是无异常的代码;)