そのため、反復中にJava HashSetから要素を削除しようとすると、ConcurrentModificationExceptionが返されます。次の例のようにHashSetから要素のサブセットを削除する最良の方法は何ですか?
Set<Integer> set = new HashSet<Integer>();
for(int i = 0; i < 10; i++)
set.add(i);
// Throws ConcurrentModificationException
for(Integer element : set)
if(element % 2 == 0)
set.remove(element);
ここに解決策がありますが、私はそれが非常にエレガントだとは思わない:
Set<Integer> set = new HashSet<Integer>();
Collection<Integer> removeCandidates = new LinkedList<Integer>();
for(int i = 0; i < 10; i++)
set.add(i);
for(Integer element : set)
if(element % 2 == 0)
removeCandidates.add(element);
set.removeAll(removeCandidates);
ありがとう!
セットの要素を手動で反復できます。
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
Integer element = iterator.next();
if (element % 2 == 0) {
iterator.remove();
}
}
for
ループではなく、while
ループを使用してこのパターンをよく目にします。
for (Iterator<Integer> i = set.iterator(); i.hasNext();) {
Integer element = i.next();
if (element % 2 == 0) {
i.remove();
}
}
人々が指摘したように、for
ループの使用は、反復子変数(この場合はi
)をより小さなスコープに限定するため、推奨されます。
ConcurrentModificationException
を取得する理由は、Iterator.remove()ではなくSet.remove()でエントリが削除されるためです。反復の実行中にSet.remove()を使用してエントリが削除されると、ConcurrentModificationExceptionが発生します。一方、Iterator.remove()によるエントリの削除は、この場合は反復がサポートされています。
新しいforループはNiceですが、残念ながらIterator参照を使用できないため、この場合は機能しません。
反復中にエントリを削除する必要がある場合は、Iteratorを直接使用する長い形式を使用する必要があります。
for (Iterator<Integer> it = set.iterator(); it.hasNext();) {
Integer element = it.next();
if (element % 2 == 0) {
it.remove();
}
}
Java 8 Collectionには、物事をより簡単で安全にするremoveIfという素敵なメソッドがあります。 APIドキュメントから:
default boolean removeIf(Predicate<? super E> filter)
Removes all of the elements of this collection that satisfy the given predicate.
Errors or runtime exceptions thrown during iteration or by the predicate
are relayed to the caller.
興味深いメモ:
The default implementation traverses all elements of the collection using its iterator().
Each matching element is removed using Iterator.remove().
最初のループを削除してソリューションをリファクタリングすることもできます。
Set<Integer> set = new HashSet<Integer>();
Collection<Integer> removeCandidates = new LinkedList<Integer>(set);
for(Integer element : set)
if(element % 2 == 0)
removeCandidates.add(element);
set.removeAll(removeCandidates);
木材が言ったように-「Java 8 CollectionにはremoveIfという素敵なメソッドがあり、それによって物事がより簡単で安全になります」
問題を解決するコードは次のとおりです。
set.removeIf((Integer element) -> {
return (element % 2 == 0);
});
これで、セットには奇数値のみが含まれます。
繰り返しながらする必要がありますか?フィルタリングまたは選択だけをしている場合は、Apache Commons CollectionUtils を使用することをお勧めします。そこには強力なツールがいくつかあり、それによってコードが「クール」になります。
必要なものを提供する実装は次のとおりです。
Set<Integer> myIntegerSet = new HashSet<Integer>();
// Integers loaded here
CollectionUtils.filter( myIntegerSet, new Predicate() {
public boolean evaluate(Object input) {
return (((Integer) input) % 2 == 0);
}});
同じ種類の述語を頻繁に使用していることに気付いた場合は、それを静的変数に引き出して再利用できます。EVEN_NUMBER_PREDICATE
のような名前を付けます。一部の人はそのコードを見て「読みにくい」と宣言するかもしれませんが、Predicateを静的に引き出すときれいに見えます。そうすれば、CollectionUtils.filter(...)
を実行していることがわかりやすく、作成中のループの束よりも(私にとっては)読みやすいように見えます。
他の可能な解決策:
for(Object it : set.toArray()) { /* Create a copy */
Integer element = (Integer)it;
if(element % 2 == 0)
set.remove(element);
}
または:
Integer[] copy = new Integer[set.size()];
set.toArray(copy);
for(Integer element : copy) {
if(element % 2 == 0)
set.remove(element);
}