ConcurrentModificationException и Хешмап

226
20 июня 2018, 03:50

Здравствуйте кидает Exception ConcurrentModificationException на этой строчке for (Transaction t : c.getTransactions())(64 строчка) на 2 итерации. Саму суть ошибки понимаю - случается она тогда, когда в цикле происходит удаление элементов итератора. Пытался remove изменить на removeIF, но ошибка та же.Это уникальный вопрос, потому что он возникает, когда все требования по его решению соблюдены.

private static void iteration() {
        Integer n = 1;
        Integer k = 1;
        Double maxProfit;
        Integer clusterInd;
        while (k > 0) {
            System.out.println("Итерация " + n);
            n++;
            k = 0;
            for (int i=0;i<clusters.size();i++) {
                Cluster c = clusters.get(i);
                for (Transaction t : c.getTransactions()) {//ошибку кидает здесь на 2 итерации
                    maxProfit = profit();
                    clusterInd = -1;
                    c.deleteTransaction(t);
                    int j = 0;
                    for (Cluster cl : clusters) {
                        if (j != i) {
                            cl.addTransaction(t);
                            Double p = profit();
                            if (p > maxProfit) {
                                maxProfit = p;
                                clusterInd = j;
                            }
                            cl.deleteTransaction(t);
                        }
                        j++;
                    }
                    if (clusterInd == -1){
                        clusters.get(i).addTransaction(t);
                    }else {
                        k++;
                        clusters.get(clusterInd).addTransaction(t);
                    }
                }
            }
        }
        System.out.println(k);
    }

Функция, в которой происходит удаление транзакции:

public void deleteTransaction(Transaction m) {
        if (this.count > 0) {
            String[] trans = m.getTrans();
            for (String s : trans) {
                this.square--;
                if (freq.containsKey(s)) {
                    if (freq.get(s) > 0) {
                        this.freq.put(s, freq.get(s) - 1);
                        if (this.freq.get(s) == 0) {
                            this.width--;
                            freq.entrySet().removeIf(entry -> entry.getKey().equals(s));
                        }
                    }
                }
            }
            this.count--;
            if (this.count > 0) {
                this.height = (double) this.square / this.width;
            } else {
                this.height = 0.0;
            }
            transactions.removeIf(m::equals);
        }
    }

Логи:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:939)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:893)
    at com.lab6.ClopeAlgorithm.iteration(ClopeAlgorithm.java:64)
    at com.lab6.ClopeAlgorithm.main(ClopeAlgorithm.java:96)
Answer 1

Создадим небольшую мапу:

final HashMap<Object, Object> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");

Следующие выражения выбьют ошибку:

Java 8+:

map.entrySet().stream().filter((i) -> ("3".equals(i.getKey()))).forEachOrdered((i) -> {
    map.remove(i.getKey());
});

Java < 8:

for (Entry<Object, Object> i : map.entrySet()) {
    if ("2".equals(i.getKey())) {
         map.remove(i.getKey());
    }
}

Выражения, которые не выдадут ошибок:
Работает только на 1 удаление, если добавить еще один if для удаления, выдаст ошибку NoSuchElementException

final Iterator<Entry<Object, Object>> i = map.entrySet().iterator();
while (i.hasNext()) {
    if ("2".equals(i.next().getKey())) {
       i.remove();
    }
}

Если хотим удалить два и более элемента, можно использовать:

while (i.hasNext()) {
    switch ((String) i.next().getKey()) {
        case"2":
        case"3":
            i.remove();
        break;                 
    }
}

Для удаления, добавления, изменения, лучше использовать switch:

final Map<Object, Object> map = new ConcurrentHashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "5");
final Iterator<Entry<Object, Object>> i = map.entrySet().iterator();
while (i.hasNext()) {
    switch ((String) i.next().getKey()) {
        case "3":
            i.remove();
            map.put("5", "5");
            final Iterator<Entry<Object, Object>> it = map.entrySet().iterator();
            while (it.hasNext()) {
                if ("2".equals((String) it.next().getKey())) {
                    it.remove();
                    map.put("5", "5");
                    map.put("6", "6");
                    map.remove(map.get("1"));
                }
            }
        break;
    }
}
System.out.println(map); // {4=5, 5=5, 6=6}

Но опять-же у этого всего есть побочные эффекты.
Если в хранилище залетел какой-то объект, во время:

while (i.hasNext()) {
    switch((String)i.next().getKey()) {
       //.....
    }
}

Получим ConcurrentModificationException.
В вашем случае можно использовать подобно mutex.

synchronized(map) {
   //....
}

Тогда можно будет не бояться, что залетает.

Answer 2

Вместо цикла foreach используйте явный итератор и его метод remove.

Answer 3

Подсказали решение: Изменить for (Transaction t : c.getTransactions()) на

for (int i=0;i<clusters.size();i++) { 
 Cluster c = clusters.get(i); 
 List<Transaction> transactions = new ArrayList<>(c.getTransactions()); 
 for (Transaction t : transactions) { 
... 
} 
} 
Answer 4

Foreach – это механизм для работы с элементами коллекции, а не с самой коллекцией. Использование foreach для модификации коллекции – это уже не правильно. А писать костыли и велосипеды, чтобы «расширить возможности» конструкции языка – и вовсе плохая затея. Этот подход сломает возможность заменить реализацию А на реализацию Б без танцев с бубном и переписывания всего кода, где коллекция модифицировалась в foreach.

Вот один из классических подходов работы с итераторами:

for (Iterator<Integer> it = set.iterator(); it.hasNext(); ) {
  if (it.next() % 2 == 0) {
    it.remove();
  }
}

Взято отсюда:

https://habr.com/post/325426/#comment_10149968

READ ALSO
Импорт библиотеки JAXB в проект JavaFX

Импорт библиотеки JAXB в проект JavaFX

Есть проект на JavaFX, для хранения данных нужно использовать сериализацию в JAXBДля этого нужно использовать XML

172
Cбои андроид приложения java.lang.RuntimeException

Cбои андроид приложения java.lang.RuntimeException

Несколько сбоев было но не могу понять изза чегоВзгляните:

153
Преобразование из int/char в String (java)

Преобразование из int/char в String (java)

String a = 'A' + "2"; Содержимое строки а: А2

181
Получения UUID Android device

Получения UUID Android device

Я в приложении для индефикации пользователя использую uuid девайсаНедавно начал использовать такую систему

201