迭代集合,避免在循环中移除时出现ConcurrentModificationExceptionexception

我们都知道你不能这样做:

for (Object i : l) { if (condition(i)) { l.remove(i); } } 

ConcurrentModificationException等…这显然有效,但并不总是。 这是一些特定的代码:

 public static void main(String[] args) { Collection<Integer> l = new ArrayList<Integer>(); for (int i=0; i < 10; ++i) { l.add(new Integer(4)); l.add(new Integer(5)); l.add(new Integer(6)); } for (Integer i : l) { if (i.intValue() == 5) { l.remove(i); } } System.out.println(l); } 

这当然会导致:

 Exception in thread "main" java.util.ConcurrentModificationException 

即使multithreading不这样做…无论如何。

这个问题最好的解决scheme是什么? 我怎样才能从一个循环中删除集合中的一个项目而不抛出这个exception?

我也在这里使用一个任意的Collection ,不一定是一个ArrayList ,所以你不能依赖get

Iterator.remove()是安全的,你可以像这样使用它:

 List<String> list = new ArrayList<>(); // This is a clever way to create the iterator and call iterator.hasNext() like // you would do in a while-loop. It would be the same as doing: // Iterator<String> iterator = list.iterator(); // while (iterator.hasNext()) { for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) { String string = iterator.next(); if (string.isEmpty()) { // Remove the current element from the iterator and the list. iterator.remove(); } } 

请注意, Iterator.remove是在迭代期间修改集合的唯一安全方法; 如果在迭代过程中以其他方式修改了底层集合,则行为是未指定的。

资源:

http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html


同样,如果你有一个ListIterator并且想要添加项目,你可以使用ListIterator#add ,出于同样的原因你可以使用Iterator#remove – 它被devise为允许它。

我傻:

 Iterator<Integer> iter = l.iterator(); while (iter.hasNext()) { if (iter.next().intValue() == 5) { iter.remove(); } } 

我认为,因为foreach循环是用于迭代的语法糖,所以使用迭代器将无济于事……但是它会为您提供.remove()function。

使用Java 8,您可以使用新的removeIf方法 。 适用于你的例子:

 Collection<Integer> coll = new ArrayList<Integer>(); //populate coll.removeIf(i -> i.intValue() == 5); 

由于这个问题已经被解决了,也就是说,最好的方法是使用迭代器对象的remove方法,我会进入抛出错误"java.util.ConcurrentModificationException"的地方的细节。

每个集合类都有一个实现Iterator接口的私有类,并提供诸如next()remove()hasNext()

接下来的代码看起来像这样…

 public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch(IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } 

这里的checkForComodification方法实现为

 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } 

所以,你可以看到,如果你明确地尝试从集合中删除一个元素。 它导致modCountexpectedModCount不同,导致ConcurrentModificationExceptionexception。

你可以像上面提到的那样直接使用迭代器,也可以保留第二个集合,并将每个要删除的项目添加到新集合中,最后删除全部。 这允许你继续使用for-each循环的types安全性,代价是增加了内存使用和CPU时间(除非你真的有很大的列表或者一台真正的旧计算机,否则不应该是个大问题)

 public static void main(String[] args) { Collection<Integer> l = new ArrayList<Integer>(); Collection<Integer> itemsToRemove = new ArrayList<Integer>(); for (int i=0; i < 10; ++i) { l.add(new Integer(4)); l.add(new Integer(5)); l.add(new Integer(6)); } for (Integer i : l) { if (i.intValue() == 5) itemsToRemove.add(i); } l.removeAll(itemsToRemove); System.out.println(l); } 

在这种情况下,一个常见的诀窍是(是?)倒退:

 for(int i = l.size() - 1; i >= 0; i --) { if (l.get(i) == 5) { l.remove(i); } } 

也就是说,我非常高兴在Java 8中有更好的方法,例如removeIf或者在stream上filter

Claudius使用for循环相同的答案:

 for (Iterator<Object> it = objects.iterator(); it.hasNext();) { Object object = it.next(); if (test) { it.remove(); } } 

使用Eclipse Collections (以前称为GS Collections ),在removeIf定义的方法removeIf将起作用:

 MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5); list.removeIf(Predicates.lessThan(3)); Assert.assertEquals(Lists.mutable.of(3, 4, 5), list); 

使用Java 8 Lambda语法,可以这样编写:

 MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5); list.removeIf(Predicates.cast(integer -> integer < 3)); Assert.assertEquals(Lists.mutable.of(3, 4, 5), list); 

Predicates.cast()的调用在这里是必要的,因为在Java 8的java.util.Collection接口上添加了一个默认的removeIf方法。

注意:我是Eclipse集合的提交者。

复制现有的列表并迭代新的副本。

 for (String str : new ArrayList<String>(listOfStr)) { listOfStr.remove(/* object reference or index */); } 

用传统的循环

 ArrayList<String> myArray = new ArrayList<>(); for (int i = 0; i < myArray.size(); ) { String text = myArray.get(i); if (someCondition(text)) myArray.remove(i); else i++; } 

我对上述问题有一个build议。 无需二级名单或任何额外的时间。 请找一个例子,以相同的东西,但以不同的方式。

 //"list" is ArrayList<Object> //"state" is some boolean variable, which when set to true, Object will be removed from the list int index = 0; while(index < list.size()) { Object r = list.get(index); if( state ) { list.remove(index); index = 0; continue; } index += 1; } 

这将避免并发exception。

如果ArrayList:remove(int index) – if(index是最后一个元素的位置),它会避免没有System.arraycopy()并且不需要时间。

如果(索引减less),arraycopy的时间会增加,而list的元素也会减less!

while(list.size()>0)list.remove(list.size()-1);最好的方法是删除它的元素以降序排列while(list.size()>0)list.remove(list.size()-1); //取O(1) while(list.size()>0)list.remove(0); //取O(阶乘(n))

 //region prepare data ArrayList<Integer> ints = new ArrayList<Integer>(); ArrayList<Integer> toRemove = new ArrayList<Integer>(); Random rdm = new Random(); long millis; for (int i = 0; i < 100000; i++) { Integer integer = rdm.nextInt(); ints.add(integer); } ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints); ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints); ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints); //endregion // region for index millis = System.currentTimeMillis(); for (int i = 0; i < intsForIndex.size(); i++) if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--); System.out.println(System.currentTimeMillis() - millis); // endregion // region for index desc millis = System.currentTimeMillis(); for (int i = intsDescIndex.size() - 1; i >= 0; i--) if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i); System.out.println(System.currentTimeMillis() - millis); //endregion // region iterator millis = System.currentTimeMillis(); for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); ) if (iterator.next() % 2 == 0) iterator.remove(); System.out.println(System.currentTimeMillis() - millis); //endregion 
  • 索引循环:1090毫秒
  • 对于desc指数: 519毫秒—最好的
  • 对于迭代器:1043毫秒

ConcurrentHashMap或ConcurrentLinkedQueue或ConcurrentSkipListMap可能是另一种select,因为即使删除或添加项目,它们也不会抛出任何ConcurrentModificationException。

ListIterator允许您添加或删除列表中的项目。 假设你有一个Car对象列表:

 List<Car> cars = ArrayList<>(); // add cars here... for (ListIterator<Car> carIterator = cars.listIterator(); carIterator.hasNext(); ) { if (<some-condition>) { carIterator().remove() } else if (<some-other-condition>) { carIterator().add(aNewCar); } } 

除了@assylias 答案 ,如果你使用Java 8,你也可以使用新的Stream api:

 List<Integer> l = Arrays.asList(4, 5, 6); static boolean condition(Integer i) { return i == 5; } static Predicate<Integer> predicate = YourClassName::condition; l.stream() .filter(predicate.negate()) .forEach(System.out::println); 

如果反转条件,则解决scheme更简洁,因为您不需要negate()谓词,因此只允许使用方法引用:

 List<Integer> l = Arrays.asList(4, 5, 6); static boolean condition(Integer i) { return i != 5; // <-- condition has been negated } l.stream() .filter(YourClassName::condition) .forEach(System.out::println); 

其中一个好处就是这个stream是懒惰的评估,即filter()操作在被terminal操作(如forEach()使用之前并不实际评估。 有关这方面的更多信息可以在Oracle的教程中find。

 for (Integer i : l) { if (i.intValue() == 5){ itemsToRemove.add(i); break; } } 

如果跳过内部iterator.next()调用,则catch从列表中移除元素。 它仍然有效! 虽然我不打算写这样的代码,它有助于理解它背后的概念:-)

干杯!

这可能不是最好的办法,但对大多数小案例来说,这应该是可以接受的:

“创build第二个空arrays,只添加你想保留的数组”

我不记得我从哪里读到这个…为了公平,我会让这个维基百科希望有人发现它,或者只是不赚取代表我不配。