Search code examples
javaexceptionconcurrentmodificationtreesetsortedset

SortedSet::removeAll( headSet ) fails, but deriving another collection from headSet succeeds. Why?


From a TreeSet (a SortedSet) in Java 8:

  1. I call the ::headSet method to get a SortedSet of the objects at the front of the sorted collection.
  2. I call ::removeAll to delete those frontmost objects.
    BAM, throws a ConcurrentModificationException.

Yet if I make another SortedSet from the headSet and pass that derived set to the ::removeAll, no problem.

Why?

Demo code using Java 8 Update 45. Enable the line //sortedSet.removeAll( headSet ); to see the exception thrown.

String cockatiel = "Cockatiel";

SortedSet sortedSet = new TreeSet< String >( );
sortedSet.add( "Dog" );
sortedSet.add( "Cat" );
sortedSet.add( "Bird" );
sortedSet.add( "Elephant" );
sortedSet.add( cockatiel );  // Passing var rather than literal.
sortedSet.add( "Guppy" );

System.out.println( "Before: " + sortedSet );

// Direct way. FAIL
SortedSet< String > headSet = sortedSet.headSet( cockatiel );
System.out.println( "headSet: " + headSet );
//sortedSet.removeAll( headSet );  // Fails. Throws java.util.ConcurrentModificationException.

// Derived way. PASS
SortedSet<String> headSetDerived = new TreeSet<String>( headSet); // Make a TreeSet from a TreeSet.
sortedSet.removeAll( headSetDerived );  // Succeeds. Why?

System.out.println( "After: " + sortedSet );

Solution

  • headSet Backed By Original Set

    In the first method, you're modifying the set (by removing elements) at the same time as iterating over it (to get the elements to remove). That throws the exception. Remember that the head set is backed by the original set, so modifying the original set ends up modifying the head set -- which you can't do while iterating over that head set.

    Excerpt from TreeSet::headSet doc (emphasis in bold is mine):

    Returns a view of the portion of this set…. The returned set is backed by this set, so changes in the returned set are reflected in this set, and vice-versa.

    Workarounds

    There are generally two ways to get around this. One is to use an Iterator directly and use its remove() method. The other is to separate the iteration and modification by first iterating to create a copy of the set and then iterating over the copy to remove from the original; this is what you did in your second approach.