SortedSet::removeAll( headSet ) 失败,但从 headSet 派生另一个集合成功。为什么?

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

来自 TreeSet (a SortedSet) 在 Java 8:

  1. 我调用 ::headSet 方法来获取排序集合前面的对象的 SortedSet
  2. 我调用 ::removeAll 删除那些最前面的对象。
    BAM,抛出 ConcurrentModificationException

然而,如果我从 headSet 创建另一个 SortedSet 并将该派生集传递给 ::removeAll,没问题。

为什么?

演示代码使用Java 8更新45。启用//sortedSet.removeAll( headSet );行以查看抛出的异常。

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 );

原始集支持的 headSet

在第一种方法中,您在修改集合(通过删除元素)的同时迭代它(以获取要删除的元素)。那会抛出异常。请记住,头部集由原始集支持,因此修改原始集最终会修改头部集——迭代该头部集时无法做到这一点。

摘自 TreeSet::headSet 文档(粗体强调是我的):

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.

解决方法

通常有两种方法可以解决这个问题。一种是直接使用 Iterator 并使用其 remove() 方法。另一种是将迭代和修改分开,首先迭代创建集合的副本,然后迭代副本以从原始集合中删除;这是您在第二种方法中所做的。