可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am seeing a weird behavior.
List 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 } }
But if i try to remove any other than second last in the list, i get ConcurrentModificationException. It came to my attention while reading "Oracle Certified Associate Java SE 7 Programmer Study Guide 2012" which incorrectly assumes that .remove() always works with an example of removing the second last in the list.
回答1:
In a list, adding or removing is considered as a modification. In your case you have made 5 modifications(additions).
‘for each’ loop works as follows,
1.It gets the iterator. 2.Checks for hasNext().
public boolean hasNext() { return cursor != size(); // cursor is zero initially. }
3.If true, gets the next element using next().
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(); }
Repeats steps 2 and 3 till hasNext() returns false.
In case if we remove an element from the list , it’s size gets reduced and modCount is increased.
If we remove an element while iterating, modCount != expectedModCount get satisfied and ConcurrentModificationException is thrown.
But removal of second last object is weird. Lets see how it works in your case.
Initially,
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.
In your case as you remove ‘d’ , size gets reduced to 4.
cursor = 4 size = 4 --> hasNext() does not succeed and next() is skipped.
In other cases, ConcurrentModificationException will be thrown as modCount != expectedModCount.
In this case, this check does not take place.
If you try to print your element while iterating, only four entries will be printed. Last element is skipped.
Hope I made clear.
回答2:
Don's use List#remove(Object)
here since you are accessing elements from the List in for-each loop.
Instead use Iterator#remove() to remove an item from List:
for(Iterator it=li.iterator(); it.hasNext();) { String str = it.next(); if(str.equalsIgnoreCase("d")) { it.remove(); //removing second last in list works fine } }
回答3:
Please use Iterator#remove()
method while removing elements from a List
while looping . Internally the for-each
loop will use the Iterator
to loop through the List
and since the behavior of an Iterator
is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling Iterator's remove() method.You are getting the exception .
This loop :
for(String str:li){ if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } }
is basically
Iterator itr = li.iterator(); while(itr.hasNext()){ String str = (String)itr.next(); if(str.equalsIgnoreCase("d")){ li.remove(str); //removing second last in list works fine } }
Why removing second last element doesn't throw exception ?
Because by removing the second last element you have reduced the size to the number of elements which you have iterated over. A basic hasNext()
implementation is
public boolean hasNext() { return cursor != size; }
So in this case the cursor=size=4
, so hasNext()
evaluates to false
and the loop breaks early before the concurrent modification check is performed in next()
. The last element is never accessed in this case . You can check that by adding a simple OR condition in the if
if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e")){ // last element "e" won't be removed as it is not accessed li.remove(str); }
But if you remove any other element next()
is called which throws the ConcurrentModificationException
.
回答4:
ConcurrentException is raised because of the fail fast behaviour of the ArrayList. This means you can not modify the list while iterating it except Iterator#remove() .
Refer http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html
回答5:
If you really want to iterate over an ArrayList
and remove elements then you should do it this way:
for(int index = yourArrayList.size() - 1; index >= 0; index--) { if(yourCondition) { yourArrayList.remove(index); } }
回答6:
You can iterate "backwards" and remove elements, but not forward. So instead of iterating from the first element to the last, iterate from the last to the first.
Pseudo-code:
for(int i = list.size() -1; i >= 0; i--) { list.remove(i); }
回答7:
If you wish to remove all then.removeAll should do the trick rather than iterating through the collection. I think it is speedier too.