我依靠列表迭代器来遍历字符列表。这是一个单线程程序,我用4种不同的方法依次使用listIterator对象。每种方法都有相同的设置:
private void myMethod(ArrayList<Integer> input) {
ListIterator<Integer> i = input.listIterator();
while (i.hasNext()) {
Integer in = i.next();
if (in < 10)
i.remove();
else
i.set(in*in); // because its lucky
}
}
使用这种模式,在第二个迭代器上抛出以下异常:
java.util.ConcurrentModificationException
然而,在javadocs中,我没有在抛出的Exception中看到这个Exception,也没有看到在完成后关闭迭代器的方法。我是否错误地使用了listIterator?我必须对同一个ArrayList进行多次迭代,每次都有条件地移除或更改每个元素。也许有一种更好的方法来迭代ArrayList,而这个用例并不是通过ListIterator来最好地解决的。
ListIterator 的java文档
这在ArrayList
javadoc中有解释,您在使用Iterator
:时使用remove()
和set()
修改列表
该类的
iterator
和listIterator
方法返回的迭代器是故障快速的:如果在迭代器创建后的任何时候对列表进行结构修改,则迭代器将抛出一个ConcurrentModificationException
,除非通过迭代器自己的remove或add方法。因此,面对并发修改,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意、不确定行为的风险。
当显示的代码显然不是产生异常的代码时,很难对问题进行诊断,因为它甚至没有编译。Iterator
的remove
方法不接受参数,set
方法是在ListIterator
上定义的,但您的代码仅将变量i
声明为Iterator
。
固定版本
private void myMethod(ArrayList<Integer> input) {
ListIterator<Integer> i = input.listIterator();
while (i.hasNext()) {
Integer in = i.next();
if (in < 10)
i.remove();
else
i.set(in*in);
}
}
运行时不会出现问题。一般问题的答案是,每次修改都会使所有现有的迭代器失效,但当您使用迭代器而不是直接使用集合接口进行修改时,用于进行修改的迭代程序除外。
但在您的代码中,只有一个迭代器,它只被创建并用于这一操作。只要迭代器对同一集合的使用不重叠,就不会有无效的问题。先前操作中存在的迭代器无论如何都会被放弃,并且后续操作中使用的迭代程序还不存在。
不过,使用更容易
private void myMethod(ArrayList<Integer> input) {
input.removeIf(in -> in < 10);
input.replaceAll(in -> in*in);
}
相反。与原始代码不同,这会进行两次迭代,但正如本答案中所解释的,在性能真正重要的情况下,removeIf
实际上会比基于迭代器的删除更快。
但问题依然存在。所示的代码不会导致ConcurrentModificationException
,因此无论这一方法是如何实现的,您的实际问题都在其他地方,并且可能仍然存在。
我对Java ListIterator的了解不够,无法回答这个问题,但我在这里似乎遇到了XY问题。Java Streams通过对原始ArrayList中的每个元素执行一个函数来移除元素或将元素映射到新的ArrayList,似乎可以更好地解决这个问题。
private ArrayList<Integer> myMethod(ArrayList<Integer> input) {
ArrayList<Integer> results = input.stream().filter(
in -> (in < 10)).collect(Collectors.toCollection(ArrayList::new));
results = input.stream().map(
in -> in*in).collect(Collectors.toCollection(ArrayList::new));
return results;
}