Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List throws ConcurrentModificationException but set does not throws ConcurrentModificationException? [duplicate]

I have below two java class

import java.util.*;

public class ArrayListTest032 {
    public static void main(String[] ar) {
        List<String> list = new ArrayList<String>();
        list.add("core java");
        list.add("php");
        list.add("j2ee");
        list.add("struts");
        list.add("hibernate");

        Iterator<String> itr = list.iterator();

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        list.remove("php");

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }

    }
}

When I run above code I get below output.

core java
php
j2ee
struts
hibernate

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at ArrayListTest032.main(ArrayListTest032.java:20)

Which is expected as I am modifying the list while iterating. But in below java class same logic is executed by set family.

import java.util.*;

public class HashSetTest021 {
    public static void main(String[] ar) {
        Set<String> set = new HashSet<String>();
        set.add("core java");
        set.add("php");
        set.add("j2ee");
        set.add("struts");
        set.add("hibernate");

        Iterator<String> itr = set.iterator();

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        set.remove("php");

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }

    }
}

And out put is.

hibernate
core java
j2ee
php
struts

There is no any ConcurrentModificationException.

I just want to know why same piece of code throws ConcurrentModificationException in case of list family, but there is no any ConcurrentModificationException in case of set family

like image 895
Rais Alam Avatar asked Feb 26 '13 14:02

Rais Alam


2 Answers

This is a kind of 'retrograde' behavior, insomuch as iterators, once fully traversed, are not reusable, aka their hasNext method should return false when you reach the end of the list.

In this case though, the iterator returned by ArrayList.iterator is an internal implementation class, with code for hasNext as follows:

public boolean hasNext() {
    return cursor != size;
}

So when you call hasNext in your second loop, it indicates (falsely) that there are more items to iterate over, because you executed an operation that changed the size of the list, after the first iteration. Semantically, you should not be able to continue iterating over items in the list after you reach the end of it, but due to this implementation detail it lets you proceed with the second while loop. Of course, at that point, you get a concurrent modification exception because of the change you made in the backing list.

On the other hand, the iterator used by your hash set has its hasNext implemented as follows:

public final boolean hasNext() {
    return next != null;
}

This implementation happens not to be as 'vulnerable' to modifications made to the hash set after an iteration has been completed, and as such the hasNext method is better behaved.

like image 191
Perception Avatar answered Sep 18 '22 12:09

Perception


This is a difference in the implementation: the iterator returned by the array list detects concurrent modifications even when it is positioned at the end, because it checks the length; iterators of the HashSet, TreeSet and LinkedList, on the other hand, do not detect this condition, because they check for being positioned at the end before checking for concurrent modification. The documentation allows iterators not to throw on concurrent modifications, so both approaches are valid.

  • Demo for the TreeSet.
  • Demo for the LinkedList.
like image 35
Sergey Kalinichenko Avatar answered Sep 21 '22 12:09

Sergey Kalinichenko