Удаление родственного брата Java вызывает исключение ConcurrentModificationException

У меня следующая проблема:

Данный:

public class A{
    Collection<B> elements = new ArrayList<B>();
}

public class B{
    Collection<B> linkedElements = new ArrayList<B>();
}

Все элементы connectedElements тоже принадлежат элементам. Я хочу, чтобы каждый раз, когда элемент удаляется из коллекции элементов, связанные с ним элементы также удаляются из этой коллекции. Я пробовал подключить наблюдателя к операции Iterator.remove и запустить там удаление связанных элементов из списка элементов, но из-за самой логики я всегда сталкиваюсь с ConcurrentModificationException.

Обновление: это код, вызывающий ошибку:

public class A{
 Collection<B> elements = new ArrayList<B>(){
    public Iterator<B> iterator() {
        return new ProxyIterator(super.iterator());
    };

  private class ProxyIterator implements Iterator{

    Iterator it;

    Object lastObject;

    public ProxyIterator(Iterator proxied){
        it = proxied;
    }

    @Override
    public boolean hasNext() {
        return it.hasNext();
    }

    @Override
    public Object next() {
        return lastObject = it.next();
    }

    @Override
    public void remove() {
        it.remove()
        for (B linkedElement : ((B)lastObject).getlinkedElements()) {
            A.this.getElements().remove(linkedElement);
        }
    }

  }
}

В этом коде простой вызов A.getElements().clear() вызовет _4 _... и все в порядке, потому что я удаляю все связанные элементы из списка элементов, удаляя при этом один единственный элемент. Вот почему мне нужен другой подход.


person Dondump    schedule 23.05.2013    source источник
comment
Покажите нам код, вызывающий эту ошибку.   -  person Daniel Kaplan    schedule 23.05.2013
comment
как вы прикрепляете этого наблюдателя к итератору?   -  person acdcjunior    schedule 23.05.2013
comment
Если linkedElements является точной копией списка elements, я предлагаю разделить список между A и B вместо синхронизации удалений.   -  person Ravi K Thapliyal    schedule 23.05.2013


Ответы (1)


Это потому, что вы изменяете массив во время итерации по нему. Из javadocs для ArrayList:

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

Итак, как только вы выполните A.this.getElements().remove(linkedElement); в методе remove, вы теперь просто структурно изменили список с помощью других средств, кроме метода «remove» итератора it, что означает, что итератор выбрасывает CME.

Разобраться с этим, вероятно, будет непросто. Я могу сразу придумать несколько вариантов, каждый из которых имеет свои сложности:

  • Переключитесь на CopyOnWriteArrayList, так как его итераторы отказоустойчивы. Обратной стороной является то, что ваш итератор может по-прежнему отображать элементы, которые были удалены ранее. (С другой стороны, вам все равно придется иметь дело с таким риском, поскольку вы могли уже пройти итерацию после дочернего элемента того, что удаляете.) Если это сработает для вас, это почти наверняка самый простой и надежный вариант.
  • Следуя циклу for в remove, замените it новым итератором, который вы вручную переместите в правильное место. Трудно сделать, если в вашем списке есть повторяющиеся элементы.
  • Реализуйте заново ArrayList.Iterator; в вашем remove методе вы можете отслеживать вносимые вами изменения и соответствующим образом обновлять итератор.

Наконец, последний вопрос / предупреждение - хотите ли вы, чтобы этот обход был рекурсивным? Прямо сейчас, если у вас есть элементы Foo, связанные с Bar, и Bar, связанные с Baz, удаление Foo из итератора приведет к удалению Bar, но нет ничего, что могло бы удалить Baz. (Если вы сначала удалили Bar, то Baz будет удален.) В общем, если вы хотите изменить поведение удаления Collection, вам лучше сделать это List.remove, а не Iterator.remove.

person Sbodd    schedule 23.05.2013
comment
Отличный ответ !. Как я уже сказал в своем вопросе, я знал, что логически иметь CME - это нормально. Мое решение было тем, которое вы указали в качестве третьего варианта. Я думаю, что глупо делать хитрый обходной путь для такой распространенной операции. Коллекции CopyOnWrite имеют действительно дорогостоящие мутационные операции. По поводу вашего последнего вопроса: нет, элемент, который имеет связанные элементы, не будет содержаться в другом списке связанных элементов (родительский элемент не может быть дочерним). - person Dondump; 27.05.2013