Java list's .remove method works only for second last object inside for each loop
我看到一个奇怪的行为。
1 2 3 4 5 6 7 8 9 10 11 | List<String> li = new ArrayList<>(); li.add("a"); li.add("b"); li.add("c"); li.add("d"); li.add("e"); for(String str:li){ if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } } |
但是,如果我试图删除列表中倒数第二位以外的任何一个,我会得到ConcurrentModificationException。在阅读"Oracle认证的JavaSee 7程序员学习指南2012"时,我注意到,错误地假定.RevEvE()总是以删除列表中的第二个最后一个例子来工作。
在列表中,添加或删除被视为修改。在你的情况下,你已经5修改(增加)。
"for each"循环的工作原理如下:
1
2 1.It gets the iterator.
2.Checks for hasNext().
1 2 3 4 | public boolean hasNext() { return cursor != size(); // cursor is zero initially. } |
3.If true, gets the next element using next().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } final void checkForComodification() { // Initially modCount = expectedModCount (our case 5) if (modCount != expectedModCount) throw new ConcurrentModificationException(); } |
重复步骤2和3,直到hasNext()返回false。
如果我们从列表中删除一个元素,它的大小就会减小,modcount也会增加。
如果我们在迭代时删除一个元素,modcount!=ExpectedModCount满足并抛出ConcurrentModificationException。
但是移除最后一个第二个物体是很奇怪的。让我们看看它在您的案例中是如何工作的。
最初,
cursor = 0 size = 5 --> hasNext() succeeds and next() also succeeds
without exception.
cursor = 1 size = 5 --> hasNext() succeeds and next() also succeeds
without exception.
cursor = 2 size = 5 --> hasNext() succeeds and next() also succeeds
without exception.
cursor = 3 size = 5 --> hasNext() succeeds and next() also succeeds
without exception.
在您的情况下,删除"d",大小将减少到4。
cursor = 4 size = 4 --> hasNext() does not succeed and next() is
skipped.
在其他情况下,ConcurrentModificationException将作为modCount引发!=预期的ODCount。
在这种情况下,不进行这种检查。
如果在迭代时尝试打印元素,则只会打印四个条目。跳过最后一个元素。
希望我说清楚。
这里不要使用
相反,使用迭代器remove()从列表中删除项:
1 2 3 4 5 6 | for(Iterator<String> it=li.iterator(); it.hasNext();) { String str = it.next(); if(str.equalsIgnoreCase("d")) { it.remove(); //removing second last in list works fine } } |
循环时,请使用
这个循环:
1 2 3 4 5 | for(String str:li){ if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } } |
基本上是
1 2 3 4 5 6 7 |
为什么删除最后一个第二个元素不会引发异常?
因为通过删除最后一个第二个元素,您已经将大小减少到迭代的元素数。基本的
1 2 3 | public boolean hasNext() { return cursor != size; } |
因此,在这种情况下,
1 2 3 4 | if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e")){ // last element"e" won't be removed as it is not accessed li.remove(str); } |
但是,如果删除任何其他元素,则调用
并发异常是由于arraylist的fail-fast行为引起的。这意味着除了迭代器remove()之外,在迭代列表时不能修改它。
请参阅http://docs.oracle.com/javase/7/docs/api/java/util/arraylist.html
如果您希望全部删除,那么.remove all应该完成这个技巧,而不是遍历集合。我觉得速度也更快。
您可以迭代"向后"并删除元素,但不能向前。因此,不要从第一个元素迭代到最后一个元素,而是从最后一个元素迭代到第一个元素。
伪代码:
1 2 3 4 | for(int i = list.size() -1; i >= 0; i--) { list.remove(i); } |
如果您真的想遍历
1 2 3 4 5 | for(int index = yourArrayList.size() - 1; index >= 0; index--) { if(yourCondition) { yourArrayList.remove(index); } } |