我遇到以下问题:
假设:
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
...并且可以,因为我在删除单个元素时从元素列表中删除所有链接元素。这就是我需要另一种方法的原因。
答案 0 :(得分:1)
这是因为你在迭代它时修改了数组。来自ArrayList的javadoc:
此类的迭代器和listIterator返回的迭代器 方法是快速失败的:如果列表在结构上被修改了 创建迭代器之后的时间,除了通过之外的任何方式 迭代器自己删除或添加方法,迭代器会抛出一个 ConcurrentModificationException的。因此,面对并发 修改,迭代器快速而干净地失败,而不是 在不确定的时间冒着任意的,非确定性的行为 在将来。
所以,一旦你在remove方法中执行A.this.getElements().remove(linkedElement);
,你现在只是通过迭代器it
的“remove”方法以外的方式对结构进行了结构修改,这意味着迭代器将抛出CME。
处理这个可能会很棘手。我可以想到一些随意的选择,所有这些都有并发症:
CopyOnWriteArrayList
,因为它的迭代器是故障安全的。缺点是您的迭代器可能仍然显示先前已删除的项目。 (另一方面,你必须要处理这种风险,因为你也可能已经过了一个孩子,无论你要移除什么。)如果这对你有用,这几乎可以肯定是最简单和最可靠的选项。remove
中执行for循环后,将it
替换为您手动前进到正确位置的新迭代器。如果您的列表允许重复项目,则很难做到。remove
方法中,您可以跟踪正在进行的更改并适当更新迭代器。最后,作为最后一个问题/警告 - 您是否希望此遍历是递归的?现在,如果您将元素Foo链接到Bar,并将Bar链接到Baz,则从迭代器中删除Foo会导致Bar被删除,但是没有什么可以删除Baz。 (如果你先删除了Bar,那么Baz就会被删除。)一般来说,如果你想修改Collection
的删除行为,你最好不要List.remove
而不是{Iterator.remove
。 1}}。