什么iterator.remove与list.remove不同,iterator不抛出异常而list.remove抛出?最后,两个都在修改集合的大小。为什么iterator.remove不会抛出ConcurrentModificationException
请忽略这里的线程。我只是谈论for-each循环和迭代器循环。据我所知,每个循环仅在内部创建迭代器。
我很困惑。
什么iterator.remove与list.remove不同,iterator不抛出异常而list.remove抛出?最后,两个都在修改集合的大小。为什么iterator.remove不会抛出ConcurrentModificationException
请忽略这里的线程。我只是谈论for-each循环和迭代器循环。据我所知,每个循环仅在内部创建迭代器。
我很困惑。
ConcurrentModificationException
不会被Iterator.remove()
抛出,因为这是允许的方式在迭代时修改集合。
如果您更改以任何其他方式进行迭代的集合,那么您可能会遇到异常。
如果在同一个集合中有两个迭代器,并且您通过其中一个迭代器删除,那么您也有可能会遇到异常。
iterator.remove从list.remove而list.remove并抛出迭代器不会抛出异常能做什么不同?
该协议是,当您通过迭代器移除时,迭代器的实现能够更新其数据结构以考虑删除。相比之下,如果通过集合对象删除(或插入或替换),则无法更新迭代器数据结构以使其与集合保持一致。
(还有,非并发集合类型不落实是线程安全的问题,所以你也可以有异常,如果集合和迭代器用于/由不同的线程更新。)
我认为你的意思是,如果你正在迭代一个列表,为什么list.remove()
会导致ConcurrentModificationException
被抛出,而iterator.remove()
不会呢?
考虑这个例子:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
if (iter.next().equals("b")) {
// iter.remove(); // #1
// list.remove("b"); // #2
}
}
如果您取消注释行#1,它会正常工作。如果您取消注释第2行(但留下#1注释),则会导致后续调用iter.next()
丢弃ConcurrentModificationException
。
原因是迭代器是一个单独的对象,它具有对基础列表内部状态的一些引用。如果在迭代器运行时修改列表,可能会导致迭代器表现不佳,例如,通过跳过元素,重复元素,索引数组末尾,等等。它试图检测这种修改,所以它会抛出ConcurrentModificationException
如果它。
删除通过迭代作品的元素,不会引起异常,因为这将更新基础列表和这指的是内部的迭代器的状态,所以一切都可以保持一致。
然而,没有什么特别的iterator.remove()
,使它在所有情况下工作。如果有多个迭代器在同一个列表上迭代,则由其中一个进行的修改将导致其他问题。考虑:
Iterator<String> i1 = list.iterator();
Iterator<String> i2 = list.iterator();
i1.remove();
i2.remove();
我们现在有两个迭代器指向同一个列表。如果我们使用其中一个修改列表,它会中断第二个操作,因此调用i2.remove()
将导致ConcurrentModificationException
。
这其实是更正确的答案 – Sarief
因为它是抛出异常的迭代器。如果您致电List.remove()
它不知道拆除,只是在其脚下发生了变化。如果您致电Iterator.remove()
,它知道当前元素已被删除,应如何处理。
下面是一个例子,说明如果集合迭代器没有检查基础集合的修改,事情可能会出错。这是ArrayLists
的迭代器是如何实现的:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) throw new NoSuchElementException();
// ...
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
// ...
ArrayList.this.remove(lastRet);
// ...
cursor = lastRet;
lastRet = -1;
}
让我们来看一个例子:
List list = new ArrayList(Arrays.asList(1, 2, 3, 4));
Iterator it = list.iterator();
Integer item = it.next();
我们删除第一个元素
list.remove(0);
如果我们想现在就打电话it.remove()
,迭代器将删除号码2,因为这是lastRet
指向的字段。
if (item == 1) {
it.remove(); // list contains 3, 4
}
这将是不正确的行为!迭代器的合约指出remove()
删除next()
返回的最后一个元素,但在并发修改的情况下无法保留它的契约。因此它选择安全并抛出异常。
其他馆藏的情况可能更加复杂。如果您修改HashMap
,则可能会根据需要增加或减少。那时候,元素将落入不同的桶中,并且迭代器保持指向桶的指针,之后重新刷新将完全丢失。
请注意iterator.remove()
本身并没有抛出异常,因为它能够更新这两个它自己的内部状态和集合。然而,在相同实例集合的两个迭代器上调用remove()
会抛出,因为它会使其中一个迭代器处于不一致状态。
我调试了迭代器实现的代码,并发现了这一点。感谢你的信息。 – javafan
迭代器工作在副本如果集合,所以如果你先使用迭代器移除元素,它将它从集合中移除并更新它的缓存并排。 – Akash5288
@ akash746 - 你有证据吗?这种方式的特定集合类型? –