7

I'm trying to delete an element from an ArrayList inside a loop.

This is OK.

ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 3));
for(Integer i: list){
    if(i == 2)
        list.remove(i);
}

But this is not, and throw concurrentMOdificationException.

 ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 3));
 for(Integer i: list){
        list.remove(i);
 }

I don't understand why.

I just added another element, it is not OK either (throw concurrentMOdificationException).

ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));

System.out.println(list);

for (Integer i : list) {
    if (i == 2)
        list.remove(i);
}
Ryan
  • 2,825
  • 9
  • 36
  • 58
  • Probably because the element `2` is not present in `list`. – rgettman May 14 '14 at 21:05
  • 4
    http://stackoverflow.com/q/8189466/738746 - related? Add one more element and it will also throw the CME. – Bhesh Gurung May 14 '14 at 21:07
  • @rgettman no, it is in the list. – Ryan May 14 '14 at 21:07
  • @Ryan Thanks for showing the contents of your `ArrayList`. – rgettman May 14 '14 at 21:09
  • I'm not really sure what you want to do here? Do you want to remove the element "2", do you want to remove whatever is at index [2] or do you want to clear the whole thing? – pennstatephil May 14 '14 at 21:10
  • @pennstatephil yes, remove element 2, not index. It does not matter, I just don't understand why I can delete in one loop, not in the other. – Ryan May 14 '14 at 21:11
  • you don't need a loop for this. try this pseudocode: `if (list.contains(x)) { list.remove(x);}` – pennstatephil May 14 '14 at 21:13
  • @pennstatephil That would not be my question. – Ryan May 14 '14 at 21:13
  • 2
    @Ryan Bhesh Gurung's linked question explains why. It looks like you can always `remove` the second-to-last element, because `next()` won't be called, because `hasNext()` will return `false` after the removal. I added a 4th element to your list, and I got a `ConcurrentModificationException` only when it wasn't the 2nd-to-last element. Also verified with 5 and 6 elements -- removing the 2nd-to-last item is the only one that doesn't cause a `ConcurrentModificationException`. – rgettman May 14 '14 at 21:21
  • @rgettman uh.., interest, let me try that. Thanks! – Ryan May 14 '14 at 21:24
  • @rgettman's summary is correct and the interesting part is `return cursor == size();` line in the `hasNext()` method. – Bhesh Gurung May 14 '14 at 21:33

2 Answers2

8

Use the Iterator class instead of the for-each loop.

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
   Integer i = it.next();
   it.remove();
}

http://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html

For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances. Some Iterator implementations (including those of all the general purpose collection implementations provided by the JRE) may choose to throw this exception if this behavior is detected. Iterators that do this are known as fail-fast iterators, as they fail quickly and cleanly, rather that risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

Community
  • 1
  • 1
stianlp
  • 999
  • 9
  • 19
  • 2
    While this may be the way to solve the problem, the OP is also interested in why the first example does NOT throw a `ConcurrentModificationException`. – rgettman May 14 '14 at 21:25
  • 2
    This is the reason why I had to protect my question. Please try to understand the motive of the question first. – Bhesh Gurung May 14 '14 at 21:29
0

You have to understand a little bit about what is going when use a for loop of this nature. It is really using a java.util.Iterator under the hood. This iterator will use the next() method to determine when iteration should stop, and the hasNext() method to retrieve the next element.

The kicker is that only next() checks for concurrent modification - hasNext() does not perform the check. In the first case, you wait until the second loop iteration to remove 2 from the list, and the next iteration finds the end of the list and exits. In the second case, you remove the 1 from the list during the first iteration, and the next iteration throws the exception when it tries to retrieve the next element.

matt forsythe
  • 3,863
  • 1
  • 19
  • 29
  • 1
    The first one remove second element actually. i is not index, i is element which is autoboxed. – Ryan May 14 '14 at 21:41
  • @Ryan I have a problem, but not the one you indicated. `i` is the the element that is autoboxed, but when you call `remove` on a list, it will get treated as an index. So `list.remove(i)` will remove the third (last) element when i = 2. However, you made me double-check my answer and I had misstated which element would be removed in the second case, so I updated my answer. – matt forsythe May 14 '14 at 21:51
  • In the first case, you wait until the second loop iteration to remove the SECOND element – Ryan May 14 '14 at 21:54
  • @Ryan Oh, wow - you are right! But only because `i` is an `Integer` and not an `int`. `ArrayList` actually has two `remove` methods. One that takes an `Object` (which it searches for in the `List`) and one that takes an `int` (which it treats an an index). That's really confusing with `List` when your enumerated type is `Integer`! – matt forsythe May 14 '14 at 21:59
  • @Ryan Updated my answer (again). Does that match your understanding of what is going on? – matt forsythe May 14 '14 at 23:04