从列表中删除项目时出现 ConcurrentModificationException
ConcurrentModificationException when removing item from a list
我有一个带有自定义列表的应用程序 class。当尝试使用以下客户参数执行 foreach 函数时:
重要!我不能修改main中的代码
主要:
XList<Integer> lmod = XList.of(1,2,8, 10, 11, 30, 3, 4);
lmod.forEachWithIndex( (e, i) -> lmod.set(i, e*2));
System.out.println(lmod);
lmod.forEachWithIndex( (e, i) -> { if (i % 2 == 0) lmod.remove(e); } );
System.out.println(lmod);
lmod.forEachWithIndex( (e, i) -> { if (i % 2 == 0) lmod.remove(i); } );
System.out.println(lmod);
XList class:
public class XList <T> extends ArrayList<T> {
public XList(Collection<T> collection) {
super(collection);
}
public XList(T... ints) {
super(Arrays.asList(ints));
}
public static <T> XList<T> of(Set<T> set) {
return new XList<>(set);
}
public static <T> XList<T> of(T... ints) {
return new XList<>(ints);
}
public void forEachWithIndex(BiConsumer<? super T, ? super Integer> consumer) {
Iterator<T> iterator = this.iterator();
int counter = 0;
while (iterator.hasNext()) {
consumer.accept(iterator.next(), counter);
counter++;
}
}
错误:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at zad1.XList.forEachWithIndex(XList.java:126)
at zad1.Main.main(Main.java:89)
ConcurrentModificationException 表示:
- 在时间点 A,您通过调用其
.iterator()
方法或让 for (var x : collection) {}
为您调用它来创建某个集合的迭代器。
- 在时间点 B,您更改了集合(而不是通过您在 A 的
.remove()
方法中创建的迭代器),例如通过调用 remove
或 add
或 clear
或 retainAll
.
- 在时间点 C,你在那个迭代器上看起来很有趣:你调用它的任何方法,或者你让 for 循环通过点击它的块的
}
来完成它。
您需要做的事情绝对不简单!
考虑一下,给定 [A、B、C、D、E] 的初始列表:您可能希望 forEachWithIndex
方法获得 运行 5 次,不管 之间的列表发生了什么:[0, A]、[1, B]、[2, C]、[3, D] 和 [4, E]。那么,如果在 [0, A]
的循环中删除 C,会发生什么情况?
有一个论点认为 [2, C]
事件根本不应该发生,事实上,剩余的循环应该是 [1, B]
、[2, D]
和[3, E]
。 因为这个问题很难回答,所以java解决了iterator()
API中的问题,只是不允许你这样做!
在 [0, A]
的循环中调用 .add("F")
时会出现类似的问题。 for 循环 运行 lambda 是否应该使用参数 [5, F]
?一个悬而未决的问题。
由您来回答这个问题,您应该非常详细地记录下来。无论你做出哪个选择,都会很艰难!
我认为 for 循环应该包括更改
这非常复杂。因为假设 C 的循环最终删除了 A。这意味着您的列表将首先使用参数 [0, A]
、[1, B]
和 [2, C]
调用 lambda,然后是下一次迭代会是什么样子?大概唯一理智的答案是 [2, D]
。为了完成这项工作,您需要跟踪各种事情 - 列表的循环代码需要知道发生了删除,因此它需要 'down-adjust'(因为您不能简单地从 0 循环到 'list size',如果您这样做,下一次迭代将是 [3, E]
并且您已完全跳过 D,即使它仍在该列表中。
泡杯咖啡,深入研究,找一块白板,画出草图。预留一整天,并注意代码将有很多页面来处理这一切。制作大量测试用例,并准确详细地描述您对所有这些测试的预期结果。
嗯,好吧,没关系。假设无论发生什么变化,它都应该迭代原始元素的所有元素
这更容易但效率低下。解决方法很简单:首先复制一份列表。然后迭代副本。副本不能更改(你是唯一一个有引用的人),所以他们可以对基础列表做任何他们想做的事情:
XList<T> copy = new XList<T>(this);
int counter = 0;
var iterator = copy.iterator();
while (iterator.hasNext()) consumer.accept(iterator.next(), counter++);
我有一个带有自定义列表的应用程序 class。当尝试使用以下客户参数执行 foreach 函数时:
重要!我不能修改main中的代码
主要:
XList<Integer> lmod = XList.of(1,2,8, 10, 11, 30, 3, 4);
lmod.forEachWithIndex( (e, i) -> lmod.set(i, e*2));
System.out.println(lmod);
lmod.forEachWithIndex( (e, i) -> { if (i % 2 == 0) lmod.remove(e); } );
System.out.println(lmod);
lmod.forEachWithIndex( (e, i) -> { if (i % 2 == 0) lmod.remove(i); } );
System.out.println(lmod);
XList class:
public class XList <T> extends ArrayList<T> {
public XList(Collection<T> collection) {
super(collection);
}
public XList(T... ints) {
super(Arrays.asList(ints));
}
public static <T> XList<T> of(Set<T> set) {
return new XList<>(set);
}
public static <T> XList<T> of(T... ints) {
return new XList<>(ints);
}
public void forEachWithIndex(BiConsumer<? super T, ? super Integer> consumer) {
Iterator<T> iterator = this.iterator();
int counter = 0;
while (iterator.hasNext()) {
consumer.accept(iterator.next(), counter);
counter++;
}
}
错误:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at zad1.XList.forEachWithIndex(XList.java:126)
at zad1.Main.main(Main.java:89)
ConcurrentModificationException 表示:
- 在时间点 A,您通过调用其
.iterator()
方法或让for (var x : collection) {}
为您调用它来创建某个集合的迭代器。 - 在时间点 B,您更改了集合(而不是通过您在 A 的
.remove()
方法中创建的迭代器),例如通过调用remove
或add
或clear
或retainAll
. - 在时间点 C,你在那个迭代器上看起来很有趣:你调用它的任何方法,或者你让 for 循环通过点击它的块的
}
来完成它。
您需要做的事情绝对不简单!
考虑一下,给定 [A、B、C、D、E] 的初始列表:您可能希望 forEachWithIndex
方法获得 运行 5 次,不管 之间的列表发生了什么:[0, A]、[1, B]、[2, C]、[3, D] 和 [4, E]。那么,如果在 [0, A]
的循环中删除 C,会发生什么情况?
有一个论点认为 [2, C]
事件根本不应该发生,事实上,剩余的循环应该是 [1, B]
、[2, D]
和[3, E]
。 因为这个问题很难回答,所以java解决了iterator()
API中的问题,只是不允许你这样做!
在 [0, A]
的循环中调用 .add("F")
时会出现类似的问题。 for 循环 运行 lambda 是否应该使用参数 [5, F]
?一个悬而未决的问题。
由您来回答这个问题,您应该非常详细地记录下来。无论你做出哪个选择,都会很艰难!
我认为 for 循环应该包括更改
这非常复杂。因为假设 C 的循环最终删除了 A。这意味着您的列表将首先使用参数 [0, A]
、[1, B]
和 [2, C]
调用 lambda,然后是下一次迭代会是什么样子?大概唯一理智的答案是 [2, D]
。为了完成这项工作,您需要跟踪各种事情 - 列表的循环代码需要知道发生了删除,因此它需要 'down-adjust'(因为您不能简单地从 0 循环到 'list size',如果您这样做,下一次迭代将是 [3, E]
并且您已完全跳过 D,即使它仍在该列表中。
泡杯咖啡,深入研究,找一块白板,画出草图。预留一整天,并注意代码将有很多页面来处理这一切。制作大量测试用例,并准确详细地描述您对所有这些测试的预期结果。
嗯,好吧,没关系。假设无论发生什么变化,它都应该迭代原始元素的所有元素
这更容易但效率低下。解决方法很简单:首先复制一份列表。然后迭代副本。副本不能更改(你是唯一一个有引用的人),所以他们可以对基础列表做任何他们想做的事情:
XList<T> copy = new XList<T>(this);
int counter = 0;
var iterator = copy.iterator();
while (iterator.hasNext()) consumer.accept(iterator.next(), counter++);