Why CopyOnWriteArrayList's iterator allows remove() in enhanced-for loop, while its iterator does not support remove() operation?

2.1k Views Asked by At

CopyOnWriteArrayList

The iterator does NOT support the remove method.

But why does it work in enhanced-for loop?

List<String> lst = new CopyOnWriteArrayList<>();
lst.add("one"); 
lst.add("two"); 
lst.add("three"); 


for (String str : lst) { 
    if (str.equals("one")) { 
        lst.remove("two");   // no ConcurrentModificationException      
    } 
}        

System.out.println(lst);   // [one, three] => removed "two" !

List<String> lst = new ArrayList<>(); would generate ConcurrentModificationException

Javadoc clearly states that CopyOnWriteArrayList.iterator() does not support remove() => it shall throw UnsupportedOperationException! I understand it is weakly consistent - say there is no ConcurrentModificationException if I add elements to CopyOnWriteArrayList after I obtained an iterator from that CopyOnWriteArrayList

P.S. Sorry, I was inattentive - I called remove() not on the iterator! I was confused by being inside enhanced-for (it uses an iterator implicitly).

2

There are 2 best solutions below

4
Jackkobec On BEST ANSWER

CopyOnWriteArrayList iterator fail safe implementation supports modify actions.

When you iterate over a CopyOnWriteArrayList and CopyOnWriteArraySet the iterator uses a snapshot of the underlying list (or set) and does not reflect any changes to the list or set after the snapshot was created. The iterator will never throw a ConcurrentModificationException.

Read more at: https://markusjais.com/java-concurrency-understanding-copyonwritearraylist-and-copyonwritearrayset/

By the way, in classic List implementation like ArrayList() etc, you don't need to use iterator explicitly. Use list.removeIf(predicate) method.

0
Yoshikage Kira On

If you try to use iterator to remove an element from CopyOnWriteArrayList. It will throw an exception. That's what javadoc is trying to say.

Code:

List<String> lst2 = new CopyOnWriteArrayList<String>();
lst2.add("one");
lst2.add("two");
lst2.add("three");

Iterator<String> iterator2 = lst2.iterator();
while (iterator2.hasNext()) {
    if (iterator2.next().equals("two")) {
        iterator2.remove();
    }
}
System.out.println(lst2.toString());

Output:

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1178)
    at MyClass.main(MyClass.java:29)

However if you do the same thing on Arraylist. It will work fine.

Source: ArrayList

Code:

List<String> lst = new ArrayList<String>();
lst.add("one");
lst.add("two");
lst.add("three");

Iterator<String> iterator = lst.iterator();
while (iterator.hasNext()) {
    if (iterator.next().equals("two")) {
        iterator.remove();
    }
}
System.out.println(lst.toString());

Output:

[one, three]

Though if you don't want to use iterator you can just use public boolean removeIf(Predicate<? super E> filter) which is just one line and gives the same output as above.

lst.removeIf(n -> (n.equals("two")));