我有一个自定义列表类的应用程序。当尝试使用customer参数执行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类:
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循环是否应该用参数[5, F]
运行一次lambda ?一个悬而未决的问题。
这个问题由你来回答,你应该详细记录下来。无论你做出哪一个选择,都将是相当困难的!
我认为for循环应该包括
这非常复杂。因为想象一下,C的循环最终删除了a。这意味着你的列表将首先调用带有参数[0, A]
、[1, B]
和[2, C]
的lambda,然后,下一次迭代会是什么样子?大概唯一合理的答案是[2, D]
。要做到这一点,你需要跟踪各种各样的事情——列表的循环代码需要意识到删除发生了,因此它需要"向下调整"(因为你不能简单地从0循环到"列表大小",如果你这样做,下一次迭代将是[3, E]
,你已经完全跳过了D,即使它仍然在那个列表中。
嗯,好吧,没关系。假设它应该对原始元素的所有元素进行迭代不管发生什么变化
这更简单,但效率低下。解决方法很简单:首先制作一个列表的副本。然后对副本进行迭代。副本不能改变(你是唯一一个有ref的人),所以他们可以对底层列表做任何他们想做的事情:
XList<T> copy = new XList<T>(this);
int counter = 0;
var iterator = copy.iterator();
while (iterator.hasNext()) consumer.accept(iterator.next(), counter++);