web-dev-qa-db-ja.com

Hashmap.keySet()、foreach、およびremove

Javaの「foreach」を使用してリストから削除することは、通常、大きな禁止事項であり、iterator.remove()を使用する必要があることを知っています。しかし、HashMapのkeySet()をループしている場合は、remove()を使用しても安全ですか?このような:

for(String key : map.keySet()) {
  Node n = map.get(key).optimize();
  if(n == null) {
   map.remove(key);
  } else {
   map.put(key, n);
  }
}
14
Sam Washburn

編集:

あなたが実際にマップにaddingしていないことに気づいていませんでした-エントリ内の値を変更しただけです。この場合、pstanton's(pre-edit1)ソリューションはほぼ正しいですが、_map.put_を呼び出すのではなく、イテレータが返したエントリでsetValueを呼び出す必要があります。 (それはpossible _map.put_が機能することを保証しますが、保証されているとは信じていません。ドキュメントでは_entry.setValue_ willが機能すると述べています。)

_for (Iterator<Map.Entry<String, Node>> it = map.entrySet().iterator(); 
     it.hasNext();)
{
    Map.Entry<String, Node> entry = it.next();
    Node n = entry.getValue().optimize();
    if(n == null) 
    {
        it.remove();
    }
    else
    {
        entry.setValue(n);
    }
}
_

entryremoveメソッドがないのは残念です。それ以外の場合は、拡張されたforループ構文を使用して、多少扱いにくくすることができます。)

古い答え

(これは、任意の変更を加えたいというより一般的なケースのためにここに残しました。)

いいえ。マップに追加したり、マップから直接削除したりしないでください。 HashSet.keySet()によって返されるセットは、スナップショットではなく、キーのビューです。

canイテレータを介して削除しますが、拡張forループではなく明示的にイテレータを使用する必要があります。

簡単なオプションの1つは、元のセットから新しいセットを作成することです。

_for (String key : new HashSet<String>(map.keySet())) {
    ...
}
_

この時点では問題ありません。セットに変更を加えていないからです。

編集:はい、キーセットイテレータを使用して要素を確実に削除できます。 HashMap.keySet()のドキュメントから:

セットは、Iterator.remove、Set.remove、removeAll、retainAll、およびclearオペレーションを介して、対応するマッピングをマップから削除する要素の削除をサポートしています。 addまたはaddAll操作はサポートしていません。

これはMapインターフェース自体の中でさえ指定されています。


1 私は、psantonについてコメントするだけでなく、私の回答を編集することにしました。類似しているが明確な状況で得られる追加情報は、この回答を維持するのに十分役立つと考えたためです。

18
Jon Skeet

エントリセットを使用する必要があります。

for(Iterator<Map.Entry<String, Node>> it = map.entrySet().iterator(); it.hasNext();)
{
      Map.Entry<String, Node> entry = it.next();
      Node n = entry.getValue().optimize();
      if(n == null) 
          it.remove();
      else
          entry.setValue(n);
}

固定コードの編集

12
pstanton