Java list .remove 方法仅适用于 foreach 循环中的倒数第二个对象[重复]
Posted
技术标签:
【中文标题】Java list .remove 方法仅适用于 foreach 循环中的倒数第二个对象[重复]【英文标题】:Java list's .remove method works only for second last object inside for each loop [duplicate] 【发布时间】:2013-04-11 09:20:56 【问题描述】:我看到了一个奇怪的行为。
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 Certified Associate Java SE 7 Programmer Study Guide 2012”时引起了我的注意,该指南错误地假设 .remove() 始终适用于删除列表中倒数第二个的示例。
【问题讨论】:
其实我知道如何删除数组中的一个元素,但我想指出为什么它只适用于倒数第二个元素,这是一个不一致行为的错误吗?? 【参考方案1】:在列表中,添加或删除被视为修改。在你的情况下,你做了 5 修改(添加)。
“for each”循环的工作原理如下,
1.It gets the iterator. 2.Checks for hasNext().
public boolean hasNext()
return cursor != size(); // cursor is zero initially.
3.如果为true,则使用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 --> hasNext() 成功并且 next() 也成功 无一例外。 cursor = 1 size = 5 --> hasNext() 成功并且 next() 也成功 无一例外。 cursor = 2 size = 5 --> hasNext() 成功并且 next() 也成功 无一例外。 cursor = 3 size = 5 --> hasNext() 成功并且 next() 也成功 毫无例外。
在您的情况下,当您删除 'd' 时,大小会减小到 4。
cursor = 4 size = 4 --> hasNext() 不成功,next() 是 跳过。
在其他情况下,ConcurrentModificationException 将作为 modCount != expectedModCount 抛出。
在这种情况下,不会进行此检查。
如果您尝试在迭代时打印元素,则只会打印四个条目。最后一个元素被跳过。
希望我说清楚了。
【讨论】:
【参考方案2】:不要在此处使用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
【讨论】:
【参考方案3】:由于 ArrayList 的快速失败行为而引发 ConcurrentException。这意味着除了 Iterator#remove() 之外,您在迭代列表时不能修改列表。
参考http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html
【讨论】:
【参考方案4】:请使用Iterator#remove()
方法从List
while 循环中删除元素。在内部,for-each
循环将使用Iterator
循环通过List
并且由于Iterator
的行为是未指定的,如果在迭代过程中以任何方式而不是通过调用Iterator's 来修改底层集合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
中添加一个简单的 OR 条件来检查这一点
if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e"))
// last element "e" won't be removed as it is not accessed
li.remove(str);
但是,如果您删除任何其他元素 next()
会抛出 ConcurrentModificationException
。
【讨论】:
【参考方案5】:如果您真的想遍历 ArrayList
并删除元素,那么您应该这样做:
for(int index = yourArrayList.size() - 1; index >= 0; index--)
if(yourCondition)
yourArrayList.remove(index);
【讨论】:
【参考方案6】:您可以“向后”迭代并删除元素,但不能向前。所以不是从第一个元素迭代到最后一个元素,而是从最后一个元素迭代到第一个元素。
伪代码:
for(int i = list.size() -1; i >= 0; i--)
list.remove(i);
【讨论】:
【参考方案7】:如果您希望全部删除。removeAll 应该做到这一点而不是遍历集合。我也觉得比较快。
【讨论】:
以上是关于Java list .remove 方法仅适用于 foreach 循环中的倒数第二个对象[重复]的主要内容,如果未能解决你的问题,请参考以下文章