如何迭代和修改 Java 集?
假设我有一组整数,并且我想递增该集合中的每个整数。我该怎么做?
是否可以在迭代集合时在集合中添加和删除元素?
我是否需要创建一个新集,以便在迭代原始集时将元素“复制并修改”到其中?
编辑:如果集合的元素是不可变的怎么办?
假设我有一组整数,并且我想递增该集合中的每个整数。我该怎么做?
是否可以在迭代集合时在集合中添加和删除元素?
我是否需要创建一个新集,以便在迭代原始集时将元素“复制并修改”到其中?
编辑:如果集合的元素是不可变的怎么办?
您可以在迭代期间使用迭代器对象安全地从集合中删除;尝试在迭代时通过其 API 修改集合将破坏迭代器。Set 类通过 getIterator() 提供迭代器。
但是,整数对象是不可变的;我的策略是循环访问集合,对于每个整数i,将i + 1添加到一些新的临时集合中。完成迭代后,从原始集中删除所有元素,然后添加新临时集中的所有元素。
Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
temp.add(i+1);
s.clear();
s.addAll(temp);
如果使用迭代器对象来遍历集合中的元素,则可以执行所需的操作。您可以随时随地删除它们,这没关系。但是,在for循环中删除它们(每种类型的“标准”)都会给您带来麻烦:
Set<Integer> set = new TreeSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
//good way:
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()) {
Integer setElement = iterator.next();
if(setElement==2) {
iterator.remove();
}
}
//bad way:
for(Integer setElement:set) {
if(setElement==2) {
//might work or might throw exception, Java calls it indefined behaviour:
set.remove(setElement);
}
}
根据@mrgloom的评论,这里有更多关于为什么上面描述的“坏”方式的细节,嗯......坏:
在不涉及太多关于Java如何实现这一点的细节的情况下,在高层次上,我们可以说“坏”的方式是坏的,因为它在Java文档中明确规定如下:
https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html
除其他外,规定(强调我的):
"例如,通常不允许一个线程修改集合,而另一个线程正在迭代它。通常,在这些情况下,迭代的结果是未定义的。某些迭代器实现(包括 JRE 提供的所有通用集合实现的实现)可能会选择在检测到此行为时引发此异常“ (...)
"请注意,此异常并不总是表示对象已被其他线程并发修改。如果单个线程发出一系列违反对象协定的方法调用,则该对象可能会引发此异常。例如,如果线程在使用快速失败迭代器迭代集合时直接修改集合,则迭代器将引发此异常。
为了更详细地介绍:可以在forEach循环中使用的对象需要实现“java.lang.Iterable”接口(javadoc here)。这将生成一个迭代器(通过在此接口中找到的“迭代器”方法),该迭代器按需实例化,并将在内部包含对创建它的迭代对象的引用。但是,当在 forEach 循环中使用可迭代对象时,此迭代器的实例对用户是隐藏的(您无法以任何方式自行访问它)。
这一点,再加上迭代器非常有状态的事实,即为了发挥其魔力并对其“next”和“hasNext”方法进行连贯的响应,它需要支持对象在迭代时不会被迭代器本身以外的其他内容更改,这使得它一旦检测到支持对象在迭代时发生了某些更改,它就会引发异常。
Java称之为“快速失败”迭代:即有一些操作,通常是那些修改可迭代实例的操作(而迭代器正在迭代它)。“快速失败”概念的“失败”部分是指迭代器检测此类“失败”操作何时发生的能力。“fail-fast”的“fast”部分(在我看来应该被称为“best-effort-fast”),一旦检测到“fail”操作已经发生,它就会通过ConcurrentModificationException终止迭代。