Подтвердить что ты не робот

Как избежать ConcurrentModificationException при переходе по карте и изменении значений?

У меня есть карта, содержащая некоторые ключи (строки) и значения (POJO)

Я хочу выполнить итерацию по этой карте и изменить некоторые данные в POJO.

Текущий код, который я унаследовал, удаляет данную запись и добавляет ее обратно после внесения некоторых изменений в POJO.

Это не сработает, так как вы не должны изменять карту во время ее итерации через нее (метод синхронизирован, но ConcurrentModificationException все еще появляется)

Мой вопрос, если мне нужно перебирать карту и изменять значения, каковы лучшие методы/методы, которые я могу использовать для этого? Чтобы создать отдельную карту и построить ее, когда я иду, верните копию?

4b9b3361

Ответ 1

Два варианта:

Вариант 1

Текущий код, который я унаследовал, удаляет данную запись и добавляет ее обратно после внесения некоторых изменений в POJO.

Вы меняете ссылку на POJO? Например, поэтому запись указывает на что-то еще полностью? Потому что, если нет, нет необходимости вообще удалять его с карты, вы можете просто изменить его.

Вариант 2

Если вам действительно нужно изменить ссылку на 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());
}

Вывод (в определенном порядке):

Посещение двух
Изменение его
Посещение одного
Посещение трех
два = DUE
один = UNO
три = тр

... без каких-либо изменений. Вероятно, вам захочется синхронизировать это, если что-то еще смотрит на эту запись и сбрасывает ее.

Ответ 2

Итерация по Map и добавление записей одновременно приведет к ConcurrentModificationException для большинства классов Map. А для классов Map, которые не выполняют (например, ConcurrentHashMap), нет гарантии, что итерация посетит все записи.

В зависимости от того, что именно вы делаете, вы можете выполнять следующие операции при повторении:

  • используйте метод Iterator.remove() для удаления текущей записи или
  • используйте метод Map.Entry.setValue() для изменения текущего значения записи.

Для других типов изменений вам может потребоваться:

  • создайте новый Map из записей текущего Map или
  • создайте отдельную структуру данных, содержащую изменения, которые необходимо внести, затем примените к Map.

И, наконец, библиотеки коллекций Google и Apache Commons Collections имеют классы утилиты для "преобразования" карт.

Ответ 3

Для таких целей вы должны использовать наборы представлений, которые предоставляет карта:

  • keySet() позволяет перебирать ключи. Это не поможет вам, так как ключи обычно неизменяемы.
  • values () - это то, что вам нужно, если вы просто хотите получить доступ к значениям карты. Если они являются изменяемыми объектами, вы можете изменить их напрямую, нет необходимости возвращать их обратно на карту.
  • entrySet() самая мощная версия, позволяет напрямую изменять значение записи.

Пример: преобразовать значения всех ключей, содержащих верхний регистр, в верхний регистр

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

Ответ 4

Создайте новую карту (mapNew). Затем перейдите по существующей карте (mapOld) и добавьте все измененные и преобразованные записи в mapNew. По завершении итерации поместите все значения из mapNew в mapOld. Это может быть недостаточно, если количество данных велико.

Или просто используйте коллекции Google - у них есть Maps.transformValues() и Maps.transformEntries().

Ответ 5

Чтобы обеспечить правильный ответ, вы должны объяснить немного больше, чего вы пытаетесь достичь.

Тем не менее, некоторые (возможно полезные) советы:

  • сделать POJO потокобезопасным и делать обновления данных на POJO напрямую. Тогда вам не нужно манипулировать картой.
  • использовать ConcurrentHashMap
  • продолжайте использовать простой HashMap, но создавайте новую карту на каждой модификации и переключайте карты за кулисами (синхронизируя работу переключателя или используя AtomicReference)

Какой подход лучше всего зависит от вашего приложения, трудно дать вам "наилучшую практику". Как всегда, создайте собственный тест с реалистичными данными.

Ответ 6

Попробуйте использовать ConcurrentHashMap.

Из JavaDoc,

Хеш-таблица, поддерживающая полный concurrency извлечения и регулируемый ожидаемый concurrency для обновления.

Для возникновения ConcurrentModificationException обычно:

в целом это недопустимо для один поток для изменения коллекции в то время как другой поток выполняет итерацию он.

Ответ 7

Другой подход, несколько замученный, заключается в использовании java.util.concurrent.atomic.AtomicReference в качестве типа вашего значения карты. В вашем случае это означает объявление вашей карты типа

Map<String, AtomicReference<POJO>>

Вам, разумеется, не нужна атомная природа ссылки, но это дешевый способ сделать слоты значений перегруппированными, не заменяя весь Map.Entry на Map#put().

Однако, прочитав некоторые другие ответы здесь, я также рекомендую использовать Map.Entry#setValue(), который мне никогда не нужен и не заметил до тех пор, пока сегодня.