-1

我有一个带有自定义列表类的应用程序。当尝试使用客户参数执行 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)
4

1 回答 1

0

ConcurrentModificationException 意味着:

  1. 在时间点 A,您可以通过调用某个集合的.iterator()方法或for (var x : collection) {}为您调用它来创建某个集合的迭代器。
  2. 在时间点 B,您更改集合(而不是通过您在 A 的.remove()方法中创建的迭代器),例如通过调用removeoraddclearor retainAll
  3. 在时间点 C,你在那个迭代器上看起来很有趣:你调用它的任何方法,或者你让 for 循环通过点击}它的块来完成它。

你需要做的绝对是不平凡的!

考虑一下,给定 [A, B, C, D, E] 的初始列表:您可能希望该forEachWithIndex方法运行 5 次,而不管中间的列表发生了什么:[0, A], [1, B]、[2, C]、[3, D] 和 [4, E]。那么如果在 for 循环期间[0, A]删除 C 会发生什么?

有一个论点是[2, C]事件根本不应该发生,事实上,剩余的循环应该是[1, B][2, D][3, E]。这是因为这很难回答,java 解决了iterator()API 中的问题,只是不允许你这样做!

.add("F")当您在循环中调用 for 时会出现类似的问题[0, A]。for 循环是否应该使用 arguments 运行 lambda 一次[5, F]?一个开放的问题。

由您来回答这个问题,并且您应该详细记录这一点。无论您做出哪种选择,都将非常困难!

我认为 for 循环应该包括更改

这非常复杂。因为假设 C 的循环最终删除了 A。这意味着您的列表将首先使用参数[0, A][1, B]和调用 lambda [2, C],然后,下一次迭代会是什么样子?大概唯一理智的答案是[2, D]。要完成这项工作,您需要跟踪各种事情 - 列表的循环代码需要知道发生了删除,因此它需要“向下调整”(因为您不能简单地从 0 循环到“列表大小” ,如果你这样做了,下一次迭代将是[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++);
于 2021-10-30T10:56:45.600 回答