java - java list.remove 适用于第一个元素,但不适用于其他元素
问题描述
众所周知,不能在 foreach 中使用 list.remove。让我感到困惑的是,阶段 1 运行正常,但阶段 2 不是。有人可以解释一下吗?
阶段1
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
System.out.println(list);
阶段2
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("2".equals(item)) {
list.remove(item);
}
}
System.out.println(list);
解决方案
疯狂的。你在java中发现了一个错误。如此简单的代码 - 它令人难以置信。
请参考 ArrayList 的源代码。具体来说,该错误位于第 962 行的 ArrayList.java 中。
通常,arraylists 有一个所谓的“mod counter”。任何时候您以任何方式修改数组列表(无论是添加、删除、清除等),modcounter 都会加一。
每当您调用时.iterator()
(for (String item :list)
在最开始时也是一次),都会创建一个新的迭代器对象(请参见上面链接中的第 947 行),并且该迭代器对象会像创建迭代器时一样存储 modcount。
这个想法是所有迭代器方法(不是很多;只有hasNext
,next
和remove
)将首先检查后备数组列表(通过调用其.iterator()
方法获得迭代器的数组列表)的 modcount 是否与记住的 modcount 不同,如果是的,迭代器将立即中止ConcurrentModificationException
.
错误是hasNext 方法无法做到这一点。我认为这是一种优化,但它导致了一个错误。
因此,发生了这种奇怪的交互:
List<String> list = new ArrayList<String>();
列表有 modcounter = 0。
list.add("1");
list.add("2");
modcounter现在是2。
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
这是语法糖。javac 将其编译为如下内容:
Iterator<String> it$1 = list.iterator();
while (it$1.hasNext()) {
String item = it$1.next();
// your actual code inside the for loop here:
if ("1".equals(item)) {
list.remove(item);
}
}
因此,让我们在此基础上进行一下:
Iterator<String> it$1 = list.iterator();
制作了一个迭代器对象;它的expectedModCount
字段设置为2
,因为那是 的当前 modcount list
。
while (it$1.hasNext()) {
迭代器的位置字段是 0,后备列表的大小是 2。所以,是的,有更多的值要返回。hasNext()
返回true,将进入while循环。
String item = it$1.next();
modcounter 被检查。expectedModCount
迭代器的值为 2,列表的 mod 计数器为 2,因此检查通过,item
设置为"1"
,并且迭代器的位置字段递增,因此现在为 1。
if ("1".equals(item)) {
list.remove(item);
}
该项目确实是“1”,因此list.remove(item)
被称为。list 将其 modcount 更新为 3,将其大小更新为 1,并"1"
从其支持数组中删除该元素。
现在奇怪的事情发生了:
while (it$1.hasNext()) {
好吧, hasNext() 不检查迭代expectedModCount
器是否仍然等于modCount
列表。如果有,这将失败,但它不会那样做。迭代器的位置字段是1
,列表的大小也是1
,所以hasNext()
返回false,退出while循环。就是这样:我们在没有遇到 ConcurrentModificationException 的情况下退出了循环。
相反,在第二个片段中,您调用next()
了两次it$1
,然后元素被删除。此时, hasNext() 被调用,然后迭代器的position
字段为 2,列表的大小为 1,具体检查(ArrayList 的第 962 行)检查 if listSize != iteratorPosition
。因此,hasNext 不是返回true(有点奇怪)。因此,while 循环第三次进入主体,运行String item = it$1.next()
,并且该next()
方法确实进行了 modCount 检查。expectedModCount
是 2,列表modCount
是 3,因此,抛出 CoModEx。
要重现这一点,您需要像这样删除数组列表中的倒数第二个元素。在您的 2 个元素的示例列表中,这将是第一个元素。
您如何在迭代期间实际删除项目?
正确的策略是使用remove
迭代器的方法。您不能在for(:)
循环中访问迭代器,因此请编写:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if ("2".equals(item)) {
it.remove(); // this is how to do it!
}
}
迭代器上的 remove() 是唯一的:假设 modcount 检查通过,它会增加迭代器的预期 modcount 以及后备列表的 mod 计数:这是您可以在迭代期间修改列表而不会让迭代器失败的唯一方法(嗯,那个,还有你发现的这个错误)。
下一步
对于这个问题,我将在 openjdk 上提交一个错误。
推荐阅读
- python - 使用机械化单击某个按钮
- google-sheets - 在满足多个条件的动态工作表中求和并除以
- c - 类型转换 long double 到 long long
- c++ - 我如何在不作为指针的情况下使我的玩家类中的函数用于红心游戏?
- python - 如何使用来自 3 列 numpy 数组(gpname,x,y)的组创建二维散点图?
- java - 如何实现 onPause() 和 onResume?
- php - Sylius:资源创建了哪些路由?
- c++ - 使用 C++ 扩展 Python/Numpy,模块在初始化时崩溃
- monitoring - 从访问日志中存储响应时间和 influxdb 中的请求数
- php - 为 PHP-C++ 项目安装 Composer