java - Java中foreach循环中的删除

  显示原文与译文双语对照的内容

这个问题已经有了如下答案:

在Java中,使用foreach循环循环遍历集合时,是否合法地调用集合是合法的? 举个例子:


List<String> names =.. ..
for (String name : names) {
//Do something
 names.remove(name).
}

作为附录,删除还没有经过迭代的项目是合法的? 例如,


//Assume that the names list as duplicate entries
List<String> names =.. ..
for (String name : names) {
//Do something
 while (names.remove(name));
}

时间:

要在遍历集合时安全地从集合中删除,你应该使用迭代器。

例如:


List<String> names =.. ..
Iterator<String> i = names.iterator();
while (i.hasNext()) {
 String s = i.next();//must be called before you can call i.remove()
//Do something
 i.remove();
}

Java文档列表中:

这个类和listIterator方法的迭代器返回的迭代器是 fail-fast: 如果在创建迭代器之后在任何时间都修改了列表,除了通过自己的迭代器移除或者添加方法之外,除了自己的迭代器之外,迭代器将抛出一个字符串。 因此,在进行并发修改时,迭代器会快速而干净地失败,而不是在未来的某个时刻冒任意的non-deterministic行为。

也许许多初学者都弄不清楚是什么的是在对于/优势是循环遍历一个列表使用foreach构造会隐式创建一个迭代器这并不一定是无法访问。 这里信息可以在这里找到

你不想这样做。 根据集合,它可能导致未定义的行为。 你想直接使用迭代器 。 虽然为每个构造是语法上的技巧,实际上是在使用迭代器,它隐藏它们,还在你的代码中无法访问该值来调用 Iterator.remove

如果在迭代过程中修改基础集合而不是调用这里方法,则迭代器的行为是未指定的。

改为编写代码:


List<String> names =.. ..
Iterator<String> it = names.iterator();
while (it.hasNext()) {

 String name = it.next();
//Do something
 it.remove();
}

注意代码调用 Iterator.remove,而不是 List.remove

附录:

即使删除了还没有遍历的元素,仍然不想修改集合,然后使用 Iterator 。 它可能以令人吃惊的方式修改集合,并影响到 Iterator 上的未来操作。

"增强的循环"的设计是不把迭代器暴露给代码,但是安全地移除一个项目的唯一方法是访问迭代器。 所以在这种情况下,你必须做旧的:


 for(Iterator<String> i = names.iterator(); i.hasNext();) {
 String name = i.next();
//Do Something
 i.remove();
 }

如果在实际代码中增强了循环的值,那么你可以将项目添加到临时集合中,然后在循环后调用列表中的removeAll 。

编辑( 附录附录): 不,在迭代时在 iterator.remove() 方法之外更改列表会引起问题。 唯一的方法是使用 CopyOnWriteArrayList,但这实际上是用于并发问题。

删除重复的最廉价的( 关于代码行) 方法是将列表转储为 LinkedHashSet ( 然后再回到一个列表如果你需要) 。 删除重复项时保留插入顺序。

我不知道迭代器,但是在这里我一直在做,直到今天从一个循环中删除一个列表中的元素:


List<String> names =.. .. 
for (i=names.size()-1;i>=0;i--) { 
//Do something 
 names.remove(i);
} 

这总是有效的,并且可以用在其他语言或者不支持迭代器的结构中。

是,你可以使用for-each循环,这样你就必须维护一个单独的列表来保持删除项,然后使用 removeAll() 方法从名称列表中删除该列表,


List<String> names =.. ..

//introduce a separate list to hold removing items
List<String> toRemove= new ArrayList<String>();

for (String name : names) {
//Do something: perform conditional checks
 toRemove.add(name);
} 
names.removeAll(toRemove);

//now names list holds expected values


for (String name : new ArrayList<>(names)) {
//Do something
 names.remove(nameToRemove);
}

在从原始列表中删除时克隆列表 names 并遍历克隆。 比上面的答案更清晰一点。

确保这不是 代码异味 。 是否可以反转逻辑,而不是'包含'?


List<String> names =.. ..
List<String> reducedNames =.. ..
for (String name : names) {
//Do something
 if (conditionToIncludeMet)
 reducedNames.add(name);
}
return reducedNames;

导致我进入这里页面的情况涉及到使用indecies从列表中删除元素的旧代码。 我想将它重构为使用foreach样式。

它通过一个完整的元素列表来验证用户有权访问哪些元素,并删除那些没有从列表中获得权限的用户。


List<Service> services =.. .
for (int i=0; i<services.size(); i++) {
 if (!isServicePermitted(user, services.get(i)))
 services.remove(i);
}

要撤消这里操作而不使用 remove:


List<Service> services =.. .
List<Service> permittedServices =.. .
for (Service service:services) {
 if (isServicePermitted(user, service))
 permittedServices.add(service);
}
return permittedServices;

"删除"什么时候会被首选? 一个考虑是,如果gien一个大的列表或者昂贵的"添加",与列表大小相比,只剩下少数删除的。 它可能更高效,只做一些删除而不是大量的添加。 但在我的情况下,这种情况并不值得这样的优化。

  1. 尝试这个 2.将条件更改为"冬季",你会感到奇怪:

public static void main(String[] args) {
 Season.add("Frühling");
 Season.add("Sommer");
 Season.add("Herbst");
 Season.add("WINTER");
 for (String s : Season) {
 if(!s.equals("Sommer")) {
 System.out.println(s);
 continue;
 }
 Season.remove("Frühling");
 }
 }

当你想从列表中删除元素时,最好使用迭代器

因为删除的源代码是


if (numMoved> 0)
 System.arraycopy(elementData, index+1, elementData, index,
 numMoved);
elementData[--size] = null;

因此,如果从列表中删除一个元素,列表将被重新构造,元素的另一个索引将被更改,这会导致你想要发生的事情。

...