Java兄弟删除抛出ConcurrentModificationException

时间:2013-05-23 19:19:59

标签: java exception collections iterator concurrentmodification

我遇到以下问题:

假设:

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

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

linkedElements的所有元素也属于元素。 我希望每次从元素集合中删除元素时,其链接元素也会从该集合中删除。 我已经尝试将一个观察者附加到Iterator.remove操作并在那里触发从元素列表中删除linkedElements,但由于逻辑本身,我总是遇到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()即可触发ConcurrentModificationException ...并且可以,因为我在删除单个元素时从元素列表中删除所有链接元素。这就是我需要另一种方法的原因。

1 个答案:

答案 0 :(得分:1)

这是因为你在迭代它时修改了数组。来自ArrayList的javadoc:

  

此类的迭代器和listIterator返回的迭代器   方法是快速失败的:如果列表在结构上被修改了   创建迭代器之后的时间,除了通过之外的任何方式   迭代器自己删除或添加方法,迭代器会抛出一个   ConcurrentModificationException的。因此,面对并发   修改,迭代器快速而干净地失败,而不是   在不确定的时间冒着任意的,非确定性的行为   在将来。

所以,一旦你在remove方法中执行A.this.getElements().remove(linkedElement);,你现在只是通过迭代器it的“remove”方法以外的方式对结构进行了结构修改,这意味着迭代器将抛出CME。

处理这个可能会很棘手。我可以想到一些随意的选择,所有这些都有并发症:

  • 切换到CopyOnWriteArrayList,因为它的迭代器是故障安全的。缺点是您的迭代器可能仍然显示先前已删除的项目。 (另一方面,你必须要处理这种风险,因为你也可能已经过了一个孩子,无论你要移除什么。)如果这对你有用,这几乎可以肯定是最简单和最可靠的选项。
  • remove中执行for循环后,将it替换为您手动前进到正确位置的新迭代器。如果您的列表允许重复项目,则很难做到。
  • 重新实现ArrayList.Iterator;在remove方法中,您可以跟踪正在进行的更改并适当更新迭代器。

最后,作为最后一个问题/警告 - 您是否希望此遍历是递归的?现在,如果您将元素Foo链接到Bar,并将Bar链接到Baz,则从迭代器中删除Foo会导致Bar被删除,但是没有什么可以删除Baz。 (如果你先删除了Bar,那么Baz就会被删除。)一般来说,如果你想修改Collection的删除行为,你最好不要List.remove而不是{Iterator.remove。 1}}。