SortedSet::removeAll( headSet ) 失败,但从 headSet 派生另一个集合成功。为什么?
SortedSet::removeAll( headSet ) fails, but deriving another collection from headSet succeeds. Why?
来自 TreeSet
(a SortedSet
) 在 Java 8:
- 我调用
::headSet
方法来获取排序集合前面的对象的 SortedSet
。
- 我调用
::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()
方法。另一种是将迭代和修改分开,首先迭代创建集合的副本,然后迭代副本以从原始集合中删除;这是您在第二种方法中所做的。
来自 TreeSet
(a SortedSet
) 在 Java 8:
- 我调用
::headSet
方法来获取排序集合前面的对象的SortedSet
。 - 我调用
::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()
方法。另一种是将迭代和修改分开,首先迭代创建集合的副本,然后迭代副本以从原始集合中删除;这是您在第二种方法中所做的。