如何在迭代此集合时避免ConcurrentModificationException?

6

我需要遍历一组项目,有时还需要同时添加到该组中。但是,如果在迭代过程中进行添加操作,则需要通过退出迭代循环并从头开始重新开始迭代来解决。然而,这会导致ConcurrentModificationException异常。[代码如下]

    List<Integer> collection = new ArrayList<>();
    for (Integer lobId: collection) {
         ..
         if (someCondition) {
             collection.add(something); 
             break; 
         }
    }

如何避免ConcurrentModificationException的情况下完成以上操作?

是否可以使用Array而非ArrayList来避免此异常?是否有任何专用集合类型可供使用?

--

编辑:

我不想为此ArrayList创建一个新副本,因为我需要多次重复此迭代过程,直到满足某些要求。每次创建新副本都会带来一些额外开销,如果可能的话,我想避免这种情况。

另外,如果可能的话,我想在该集合中保持排序和唯一值。库中是否有任何即可使用的东西?否则,我可以在迭代过程结束时对其进行排序并删除重复项。这对我也没问题。


2
请参考以下链接:https://dev59.com/Omkw5IYBdhLWcg3wkLX2 - Vadim
@user01 根据您的新要求修改了我的回复。 - jax
5个回答

4

请使用另一个集合进行添加,最后再进行组合。

List<Integer> collection = new ArrayList<>();
collection.add(...)
...
List<Integer> tempCollection = new ArrayList<>();    
for (Integer lobId: collection ) {
     ..
     if (someCondition) {
         tempCollection.add(something); 
         break; 
     }
}

collection.addAll(tempCollection);

迭代器的接口文档可以在http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html找到,它是任何基本Java证书的一部分。复制列表不仅是不良实践,而且会导致重大安全漏洞。 - Hannes
@Hannes 我没有复制列表。 - jax

3

这段代码不会引发ConcurrentModificationException,因为在添加元素后,您会终止循环并停止使用迭代器。


但实际上确实如此。但是等等..也许我在这里简化了它。它还包含了在迭代时调用addAll()方法的操作!? - Rajat Gupta
1
您在我正在探索该选项时删除了有关"listIterator"的答案,这是否仍然有效? - Rajat Gupta
ListIterator.add 实际上会在当前索引处插入一个元素,这可能不是你想要的。 - Evgeniy Dorofeev
“这段代码不会导致ConcurrentModificationException..” - 是的,我进行了调查并发现,当执行<arraylist>.addAll(EMPTY_LIST)时,如果没有使用break关键字,就会导致ConcurrentModificationException异常。 - Rajat Gupta

2

ConcurrentModificationException 的基本意思是您正在遍历一个Collection,并且使用一个 iterator 进行迭代(虽然这个迭代器是隐式定义的,由增强的 for 循环生成),并且在运行时通过更改 Collection 使其无效。

您可以通过通过 相同的iterator 进行修改来避免此问题:

List<Integer> collection = new ArrayList<>();
ListIterator<Integer> iter = collection.listIterator();
while (iter.hasNext()) {
     Integer currVal = iter.next();
     if (someCondition) {
         iter.add(something); // Note the addition is done on iter
         break; 
     }
}

迭代器(Iterator)从来没有 add 方法,只有 ListIterator 才有。将你的 Iterator 更改为 ListIterator,这个答案就正确了。由于 user01 表示他有一个 List,所以这个方法可以使用。如果他使用的是其他集合类型,那么这个方法就不适用。 - Olivier Grégoire
@ogregoire 确实,你是正确的。我没有注意到。已经修复了 - 谢谢你的提醒! - Mureinik

2
如果我理解您的意思正确,您希望在列表上进行迭代,如果满足某些条件,您想要中断迭代,并从一个新项目开始。
对于此情况,请执行以下操作:
   List<Integer> collection = new ArrayList<>();
   boolean flag = false;
   Integer item = 
    for (Integer lobId: collection) {
         ..
         if (someCondition) {
             flag = true;
             item = something; 
             break; 
         }
    }

   if (flag){
      collection.add(item);
   }

如果有其他人要在我们的循环之外更改列表 - 您需要同步这些访问 - 阅读迭代器线程安全,并像这里的其他答案一样使用复制列表或一些其他写时复制。


-1

不要使用 for each,使用老式的方法

for(int i=0; i<collection.size();i++)

它并没有回答他的问题。 - Martin Tuskevicius
你是指这个问题吗?“我怎么可能避免ConcurrentModificationException并实现上述操作?” - Camilo
1
  1. 好的,我的错,我假设他/她正在遍历一个列表,可能是因为他明确声明他正在遍历一个ArrayList。
  2. 不,在这种情况下,您会收到ConcurrentModificationException,因为foreach循环使用列表的迭代器,并且作为ArrayList,它是一个快速失败的迭代器。使用我的答案,您不会使用迭代器,因此不会出现异常。
- Camilo
而且,在循环中修改ArrayList并不好,但他明确表示在修改列表后,他会退出循环,所以没有任何伤害。我认为这个答案和@Mureinick到目前为止是最好的答案,有些人应该在点击之前尝试阅读。 - Camilo
你的回答相当不完整,但 OP 确实正在向 ArrayList 添加项目。因此,Listterator 解决方案执行了不同的操作。@MartinTuskevicius:他将变量称为“collection”,但将其初始化为ArrayList。无论如何,这个答案比使用ListIterator更正确,因为它保持了列表顺序。 - maaartinus
显示剩余2条评论

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