いくつかのキー(文字列)と値(POJO)を含むマップを持っています
このマップを繰り返し処理して、POJOの一部のデータを変更します。
私が継承した現在のコードは、指定されたエントリを削除し、POJOにいくつかの変更を加えた後にそれを追加します。
反復処理中にマップを変更するべきではないため、これはうまく機能しません(メソッドは同期されますが、ConcurrentModificationExceptionは引き続き表示されます)。
私の質問は、マップを反復して値を変更する必要がある場合、そのために使用できるベストプラクティス/メソッドは何ですか?別のマップを作成し、それを作成しながら作成するには、コピーを返しますか?
2つのオプション:
私が継承した現在のコードは、指定されたエントリを削除し、POJOにいくつかの変更を加えた後にそれを追加します。
参照をPOJOに変更していますか?たとえば、エントリは完全に別のものを指しているのですか?そうでない場合は、マップから削除する必要がないため、変更するだけです。
doが実際にPOJOへの参照を変更する必要がある場合(たとえば、エントリの値)、_Map.Entry
_ entrySet()
のインスタンス。エントリでsetValue
を使用できます。これにより、反復する対象が変更されることはありません。
例:
_Map<String,String> map;
Map.Entry<String,String> entry;
Iterator<Map.Entry<String,String>> it;
// Create the map
map = new HashMap<String,String>();
map.put("one", "uno");
map.put("two", "due");
map.put("three", "tre");
// Iterate through the entries, changing one of them
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
System.out.println("Visiting " + entry.getKey());
if (entry.getKey().equals("two"))
{
System.out.println("Modifying it");
entry.setValue("DUE");
}
}
// Show the result
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
_
出力(順不同)は次のとおりです。
ふたりを訪ねる
それを修正する
訪問
3人を訪問
2 = DUE
one = uno
three = tre
...変更の例外はありません。他の何かもそのエントリを見て/いじくっている場合に備えて、おそらくこれを同期させたいでしょう。
Map
を繰り返し処理し、同時にエントリを追加すると、ほとんどのConcurrentModificationException
クラスでMap
が生成されます。そして、そうでないMap
クラス(たとえば、ConcurrentHashMap
)の場合、反復がすべてのエントリを訪問するという保証はありません。
それが何をしているのかに応じて、反復しながら次のことができる場合があります。
Iterator.remove()
メソッドを使用して現在のエントリを削除する、またはMap.Entry.setValue()
メソッドを使用して、現在のエントリの値を変更します。他の種類の変更については、次の操作が必要になる場合があります。
Map
のエントリから新しいMap
を作成する、またはMap
に適用します。そして最後に、GoogleコレクションとApache Commonsコレクションライブラリには、マップを「変換」するためのユーティリティクラスがあります。
このような目的のために、マップが公開するコレクションビューを使用する必要があります。
例:大文字を含むすべてのキーの値を大文字に変換する
for(Map.Entry<String, String> entry:map.entrySet()){
if(entry.getKey().contains("_"))
entry.setValue(entry.getValue().toUpperCase());
}
実際、値オブジェクトを編集するだけの場合は、値コレクションを使用して編集します。あなたの地図はタイプ<String, Object>
:
for(Object o: map.values()){
if(o instanceof MyBean){
((Mybean)o).doStuff();
}
}
新しいマップを作成します(mapNew)。次に、既存のマップ(mapOld)を反復処理し、変更および変換されたすべてのエントリをmapNewに追加します。反復が完了したら、mapNewからmapOldまでのすべての値を入力します。ただし、データ量が多い場合は、これでは不十分な場合があります。
または、単に Googleコレクション を使用します-それらにはMaps.transformValues()
およびMaps.transformEntries()
があります。
適切な答えを提供するために、達成しようとしていることをもう少し説明する必要があります。
それでも、いくつかの(おそらく役立つ)アドバイス:
どのアプローチが最適であるかは、アプリケーションに大きく依存します。「ベストプラクティス」を提供することは困難です。いつものように、現実的なデータで独自のベンチマークを作成します。
ConcurrentHashMap を使用してみてください。
JavaDocから、
取得の完全な同時実行性と更新の予想される同時実行性をサポートするハッシュテーブル。
ConcurrentModificationExceptionが発生するには、通常:
別のスレッドがコレクションを反復している間に、あるスレッドがコレクションを変更することは、一般的に許可されていません。
やや拷問される別のアプローチは、マップの値の型として Java.util.concurrent.atomic.AtomicReference
を使用することです。あなたの場合、それはあなたのタイプのマップを宣言することを意味します
Map<String, AtomicReference<POJO>>
確かに参照のatomic性質は必要ありませんが、Map.Entry
全体を置き換えることなく値スロットを再バインド可能にする安価な方法ですMap#put()
。
それでも、ここで他のいくつかの応答を読んだので、私は Map.Entry#setValue()
の使用をお勧めします。