我看到了一种奇怪的行为。
List<String> li = new ArrayList<>();
li.add("a");
li.add("b");
li.add("c");
li.add("d");
li.add("e");
for(String str:li){
if(str.equalsIgnoreCase("d")){
li.remove(str); //removing second last in list works fine
}
}
但是如果我尝试删除列表中的第二个以外的任何其他内容,我会收到ConcurrentModificationException。在阅读“Oracle认证助理Java SE 7程序员学习指南2012”时,我注意到它错误地假设.remove()总是使用删除列表中倒数第二个的示例。
答案 0 :(得分:13)
在列表中,添加或删除被视为修改。在你的情况下,你做了 5个修改(补充)。
每个'循环的'的工作原理如下,
1.It gets the iterator. 2.Checks for hasNext().
public boolean hasNext()
{
return cursor != size(); // cursor is zero initially.
}
3.如果是,请使用next()获取下一个元素。
public E next()
{
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification()
{
// Initially modCount = expectedModCount (our case 5)
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
重复步骤2和3,直到hasNext()返回false。
如果我们从列表中删除一个元素,它的大小会减少并且modCount会增加。
如果我们在迭代时删除元素,则modCount!= expectedModCount得到满足 抛出了ConcurrentModificationException。
但删除第二个最后一个对象很奇怪。让我们看看它是如何工作的。
最初,
cursor = 0 size = 5 - &gt; hasNext()成功,next()也成功 毫无例外。
cursor = 1 size = 5 - &gt; hasNext()成功,next()也成功 毫无例外。
cursor = 2 size = 5 - &gt; hasNext()成功,next()也成功 毫无例外。
cursor = 3 size = 5 - &gt; hasNext()成功,next()也成功 没有例外。
在你删除'd'的情况下,大小会减少到4。
cursor = 4 size = 4 - &gt; hasNext()不成功,next()不成功 跳过。
在其他情况下,ConcurrentModificationException将作为modCount!= expectedModCount抛出。
在这种情况下,不会进行此项检查。
如果您尝试在迭代时打印元素,则只会打印四个条目。跳过最后一个元素。
希望我说清楚。
答案 1 :(得分:6)
Don在这里使用List#remove(Object)
,因为您在for-each循环中访问List中的元素。
而是使用Iterator#remove()从列表中删除项目:
for(Iterator<String> it=li.iterator(); it.hasNext();) {
String str = it.next();
if(str.equalsIgnoreCase("d")) {
it.remove(); //removing second last in list works fine
}
}
答案 2 :(得分:3)
请在循环时从Iterator#remove()
删除元素时使用List
方法。在内部,for-each
循环将使用Iterator
循环遍历List
,因为如果在迭代进行过程中修改了基础集合,则未指定Iterator
的行为除了通过调用Iterator的remove()方法之外的任何其他方式。你得到了异常。
这个循环:
for(String str:li){
if(str.equalsIgnoreCase("d")){
li.remove(str); //removing second last in list works fine
}
}
基本上是
Iterator<String> itr = li.iterator();
while(itr.hasNext()){
String str = (String)itr.next();
if(str.equalsIgnoreCase("d")){
li.remove(str); //removing second last in list works fine
}
}
为什么删除第二个最后一个元素不会抛出异常?
因为通过删除第二个最后一个元素,您已将大小减小到迭代的元素数。基本的hasNext()
实施是
public boolean hasNext() {
return cursor != size;
}
因此,在这种情况下cursor=size=4
,hasNext()
评估为false
,并且在next()
中执行并发修改检查之前,循环会中断。在这种情况下,永远不会访问最后一个元素。您可以通过在if
if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e")){
// last element "e" won't be removed as it is not accessed
li.remove(str);
}
但是如果删除任何其他元素next()
,则会调用ConcurrentModificationException
。
答案 3 :(得分:2)
由于ArrayList的快速失败行为而引发ConcurrentException。这意味着除了Iterator #remove()。
之外,您无法在迭代时修改列表参考http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html
答案 4 :(得分:0)
如果您真的要迭代ArrayList
并删除元素,那么您应该这样做:
for(int index = yourArrayList.size() - 1; index >= 0; index--) {
if(yourCondition) {
yourArrayList.remove(index);
}
}
答案 5 :(得分:0)
你可以向后迭代&#34;并删除元素,但不转发。因此,不是从第一个元素迭代到最后一个元素,而是从最后一个元素迭代到第一个元素。
的伪代码:
for(int i = list.size() -1; i >= 0; i--)
{
list.remove(i);
}
答案 6 :(得分:0)
如果您希望删除所有内容。 removeAll 应该执行操作,而不是遍历集合。我觉得它也更快。