迭代集合,避免在循环中移除时出现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(); }
所以,你可以看到,如果你明确地尝试从集合中删除一个元素。 它导致modCount
与expectedModCount
不同,导致ConcurrentModificationException
exception。
你可以像上面提到的那样直接使用迭代器,也可以保留第二个集合,并将每个要删除的项目添加到新集合中,最后删除全部。 这允许你继续使用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,只添加你想保留的数组”
我不记得我从哪里读到这个…为了公平,我会让这个维基百科希望有人发现它,或者只是不赚取代表我不配。