[英]ConcurrentModificationException when removing item from a list
I have an application with custom list class.我有一个带有自定义列表类的应用程序。 When trying to do a foreach function with customer argument following happens:当尝试使用客户参数执行 foreach 函数时,会发生以下情况:
Important!重要的! I cannot modify code in main我无法修改 main 中的代码
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: XList 类:
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++;
}
}
Error:错误:
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 means: ConcurrentModificationException 意味着:
.iterator()
method, or having a for (var x : collection) {}
call it for you.在时间点 A,您可以通过调用其.iterator()
方法或让for (var x : collection) {}
为您调用它来.iterator()
某个集合的迭代器。.remove()
method), for example by invoking remove
or add
or clear
or retainAll
.在时间点 B,您更改集合(而不是通过您在 A 的.remove()
方法中创建的迭代器),例如通过调用remove
或add
或clear
或retainAll
。}
of its block.在时间点 C,你对那个迭代器看起来很有趣:你调用它的任何方法,或者你让 for 循环通过点击它的块的}
来完成它。What you need to do is decidedly nontrivial!你需要做的绝对是不平凡的!
Think about it, given an initial list of [A, B, C, D, E]: you'd presumably want that forEachWithIndex
method to get run 5 times, regardless of what happens to the list in between: [0, A], [1, B], [2, C], [3, D], and [4, E].考虑一下,给定一个 [A, B, C, D, E] 的初始列表:您可能希望forEachWithIndex
方法运行 5 次,无论列表之间发生了什么:[0, A] , [1, B], [2, C], [3, D], 和 [4, E]。 So what should happen if, during the loop for [0, A]
, you remove C?那么如果在[0, A]
的循环中删除 C 会发生什么?
There's an argument to be made that the [2, C]
event shouldn't happen at all, and, in fact, that the remaining loops should be [1, B]
, [2, D]
, and [3, E]
.有一个论点认为[2, C]
事件根本不应该发生,事实上,剩余的循环应该是[1, B]
、 [2, D]
和[3, E]
. It's because this is so hard to answer that java solved the problem in the iterator()
API by simply not allowing you to do it!这是因为这个问题很难回答,所以 java 通过简单地不允许你这样做来解决iterator()
API 中的问题!
Similar question comes up when you call .add("F")
during the loop for [0, A]
.当您在[0, A]
的循环中调用.add("F")
时,会出现类似的问题。 Should the for loop run the lambda once with arguments [5, F]
, or not? for 循环是否应该使用参数[5, F]
运行一次 lambda? An open question.一个开放的问题。
It's up to you to answer this question, and you should document this in excruciating detail.由您来回答这个问题,您应该详细地记录下来。 No matter which choice you make it'll be quite difficult!无论你做出哪个选择,都将是相当困难的!
This is incredibly complicated.这是非常复杂的。 Because imagine that the loop for C ends up removing A. That means that your list will first invoke the lambda with arguments [0, A]
, [1, B]
, and [2, C]
, and then, what's the next iteration going to look like?因为想象 C 的循环最终删除了 A。这意味着您的列表将首先使用参数[0, A]
、 [1, B]
和[2, C]
调用 lambda,然后,下一次迭代是什么会是什么样子? Presumably the only sane answer then is [2, D]
.大概唯一合理的答案是[2, D]
。 To make this work you need to track all sorts of things - the list's loop code needs to be aware that deletions happened and that therefore it needs to 'down-adjust' (because you can't simply loop from 0 to 'list size', if you did that, the next iteration would be [3, E]
and you have skipped D entirely even though it is still in that list.为了完成这项工作,您需要跟踪各种事情 - 列表的循环代码需要知道删除发生了,因此它需要“向下调整”(因为您不能简单地从 0 循环到“列表大小” ,如果你这样做了,下一次迭代将是[3, E]
并且你已经完全跳过了 D,即使它仍然在那个列表中。
Make some coffee, dig in, find a whiteboard, sketch this out.煮点咖啡,深入研究,找一块白板,勾勒出来。 Reserve a full day and be aware that the code will be many pages to deal with it all.预留一整天,并注意代码将有很多页来处理这一切。 Make a TON of test cases and describe in detail precisely what you expect to happen for all of these.制作大量测试用例,并准确描述您对所有这些用例的期望。
This is easier but inefficient.这更容易但效率低下。 The fix is simple: First make a copy of the list.解决方法很简单:首先制作列表的副本。 Then iterate the copy.然后迭代复制。 The copy cannot change (you're the only one with a ref to it), so they can do whatever they want to the underlying list:该副本无法更改(您是唯一一个对其有引用的人),因此他们可以对基础列表执行任何他们想做的事情:
XList<T> copy = new XList<T>(this);
int counter = 0;
while (iterator.hasNext()) consumer.accept(iterator.next(), counter++);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.