Reputation: 663
I am iterating elements of ArrayList using Iteratot.While iterating I am adding an element into the ArrayList at the end. But getting an error : ConcurrentModificationException.
If I change ArrayList to Set and tried to add elements in set at the end during iteration no error is throwing
Test.java
class Test {
public static void main() {
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(20);
al.add(30);
al.add(40);
Iterator<Integer> it = marks.iterator();
while (it.hasNext()) {
int value = it.next();
if (value == 40) al.add(50);
}
}
}
Test2.java
class Test2 {
public static void main() {
Set<Integer> marks = new HashSet<>();
marks.add(20);
marks.add(30);
marks.add(40);
Iterator<Integer> it = marks.iterator();
while (it.hasNext()) {
int value = it.next();
if (value == 40) marks.add(50);
}
}
}
Upvotes: 0
Views: 5141
Reputation: 140328
It is mere fluke that you don't get a ConcurrentModificationException
with HashSet
. The reason for that fluke is that you happen to add the element on the last iteration of the loop.
This is far from guaranteed to happen, because HashSet
has no ordering guarantees. Try adding or removing some more elements from the list, and you'll find that eventually be able to get a ConcurrentModificationException
, because the 40
won't be the last element in the list.
The simplest way to reproduce this is to work with just a single element (since then the ordering of the HashSet
is known):
HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(1);
for (int i : hashSet) {
hashSet.add(2);
}
This won't throw a ConcurrentModificationException
.
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
for (int i : list) {
list.add(2);
}
This will throw a ConcurrentModificationException
.
As to the reason why you don't get the exception when adding to a HashSet
on the last iteration,
look at the source code for the iterator returned by HashSet.iterator()
:
abstract class HashIterator {
// ...
public final boolean hasNext() {
return next != null;
}
// ...
}
So, the hasNext()
value is precomputed, based on next
; this is set a bit lower down in the code:
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
// ^ Here
}
return e;
}
So, the value of hasNext
is determined when the previous element of the iteration is determined.
Since your code is:
Iterator it = marks.iterator();
while (it.hasNext()) {
int value = it.next();
if (value == 40)
marks.add(50);
}
the hasNext()
is called after the loop iteration in which the element is added; but next
was set to null when it.next()
was called - before the call to add
- and so hasNext()
will return false, and the loop exits without ConcurrentModificationException
.
On the other hand, the iterator of ArrayList
uses the size of the underlying list to determine the return value of hasNext()
:
public boolean hasNext() {
return cursor != size;
// ^ current position of the iterator
// ^ current size of the list
}
So this value is "live" with respect to increasing (or decreasing) the size of the underlying list. So, hasNext()
would return true after adding the value; and so next()
is called. But the very first thing that next()
does is to check for comodification:
public E next() {
checkForComodification();
// ...
And thus the change is detected, and a ConcurrentModificationException
is thrown.
Note that you wouldn't get a ConcurrentModificationException
with ArrayList
if you modified it on the last iteration, if you were to add and remove an element (or, rather, to add and remove equal numbers of elements, so that the list's size is the same afterwards).
Upvotes: 1
Reputation: 1619
There is probably a better solution, but this one is working as well. A second list is used and then added to the original one, after the loop has finished.
Main
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> al = new ArrayList<Integer>();
List<Integer> toAdd = new ArrayList<Integer>();
int stepSize = 10;
al.add(20);
al.add(30);
al.add(40);
Iterator<Integer> it = al.iterator();
while (it.hasNext()) {
int value = it.next();
if (value == al.get(al.size() - 1)){
toAdd.add(value + stepSize);
}
}
// Add all elements
al.addAll(toAdd);
// Print
al.forEach(System.out::println);
}
}
In case you only want to append one element based on the last value, as in your example, you could also wait until the loop is finished (if you still need it) and try to add it like this:
al.add(al.get(al.size() - 1) + stepSize);
Output
20
30
40
50
Upvotes: 5
Reputation: 1878
Yes , This is expected for normal List.
You can use CopyOnWriteArrayList
for concurrent activity on List.
For example , click here
Upvotes: 0