ConcurrentModificationException при использовании итератора и iterator.remove()

    private int checkLevel(String bigWord, Collection<String> dict, MinMax minMax)
{
    /*value initialised to losing*/
    int value = 0; 
    if (minMax == MinMax.MIN) value = 1; 
    else value = -1; 


    boolean go = true;

    Iterator<String> iter = dict.iterator();

    while(iter.hasNext())
    {
        String str = iter.next(); 
        Collection<Integer> inds = naiveStringSearch(bigWord, str);

        if(inds.isEmpty())
        {
            iter.remove();
        }

        for (Integer i : inds)
        {
            MinMax passin = minMax.MIN;
            if (minMax == MinMax.MIN) passin = minMax.MAX;

            int value2 = checkLevel(removeWord(bigWord, str, i), dict, passin); 
            if (value2 == -1 && minMax == minMax.MIN)
            {
                value = -1; 
                go = false;
            }
            if (value2 == 1 && minMax == minMax.MAX)
            {
                value = 1; 
                go = false; 
            }

        }

        if (go == false) break; 
    }


    return value;
}

Ошибка:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:810)
at java.util.HashMap$KeyIterator.next(HashMap.java:845)
at aStringGame.Main.checkLevel(Main.java:67)
at aStringGame.Main.test(Main.java:117)
at aStringGame.Main.main(Main.java:137)

В чем проблема?


person dwjohnston    schedule 03.12.2012    source источник
comment
Что вы делаете в checkLevel?   -  person kosa    schedule 04.12.2012
comment
@Nambari - я обновил код, чтобы показать весь метод. Это рекурсивный метод.   -  person dwjohnston    schedule 04.12.2012


Ответы (4)


Что-то где-то модифицирует dict. Я подозреваю, что это может происходить внутри этого вызова:

int value2 = checkLevel(removeWord(bigWord, str, i), dict, passin);
                                                     ^^^^

edit По сути, рекурсивный вызов checkLevel() изменяет dict через другой итератор. Это приводит к отказоустойчивому поведению внешнего итератора.

person NPE    schedule 03.12.2012
comment
^ Я обновил вопрос, чтобы показать, что это рекурсивный метод. Каким было бы лучшее решение? Клонировать словарь, который я передаю? - person dwjohnston; 04.12.2012
comment
@jahroy - но коллекция представляет собой хэш-набор. (Причина того, что это хеш-набор, заключается в том, что я беспокоюсь о производительности, я не уверен, что хэш-набор будет быстрым для простой итерации и удаления элементов, но). - person dwjohnston; 04.12.2012
comment
Если вы повторяете, все коллекции равны с точки зрения производительности. Кстати, если вы используете параллельный набор, у вас не будет этой проблемы. - person Peter Lawrey; 04.12.2012
comment
Набор улучшит производительность, гарантируя отсутствие дубликатов. Если вы создаете список из HashSet, у списка также не будет дубликатов. - person jahroy; 04.12.2012
comment
@user1068446 user1068446 - Если вас беспокоит преобразование между Set и List, ознакомьтесь со второй половиной моего ответа. - person jahroy; 04.12.2012

Вы не можете изменить Коллекцию, пока перебираете ее с помощью Итератора.

Ваша попытка вызвать iter.remove() нарушает это правило (это может сделать и ваш метод removeWord).

Вы МОЖЕТЕ изменять список во время итерации, ЕСЛИ вы используете ListIterator для повторения.

Вы можете преобразовать свой набор в список и использовать итератор списка:

List<String> tempList = new ArrayList<String>(dict);
ListIterator li = tempList.listIterator();

Другой вариант — отслеживать элементы, которые вы хотите удалить во время итерации.

Например, вы можете поместить их в набор.

Затем вы можете вызвать dict.removeAll() после цикла.

Пример:

Set<String> removeSet = new HashSet<String>();
for (String s : dict) {
    if (shouldRemove(s)) {
        removeSet.add(s);
    }
}
dict.removeAll(removeSet);
person jahroy    schedule 03.12.2012

При использовании цикла for each вам не разрешено изменять Collection, который вы повторяете внутри цикла. Если вам нужно изменить его, используйте классический цикл for.

person andreih    schedule 03.12.2012
comment
Это правда, что традиционный цикл for позволит избежать ConcurrentModificationException. Однако вы не можете получить доступ к элементам в наборе по их индексу. - person jahroy; 04.12.2012

Это обычное явление во всех классах Collections. Например, запись в TreeSet использует отказоустойчивый метод.

Итераторы, возвращаемые методом итератора этого класса, являются отказоустойчивыми: если набор изменяется в любое время после создания итератора любым способом, кроме как с помощью собственного метода удаления итератора, итератор выдает исключение ConcurrentModificationException. Таким образом, перед лицом одновременной модификации итератор быстро и чисто дает сбой, а не рискует произвольным, недетерминированным поведением в неопределенное время в будущем.

http://docs.oracle.com/javase/6/docs/api/java/util/TreeSet.html

person CodeDreamer    schedule 03.12.2012