在 Java 的 foreach 循环中调用 remove

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1196586/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 00:47:13  来源:igfitidea点击:

Calling remove in foreach loop in Java

javaloopsiteratorforeach

提问by Michael Bobick

In Java, is it legal to call remove on a collection when iterating through the collection using a foreach loop? For instance:

在 Java 中,在使用 foreach 循环遍历集合时对集合调用 remove 是否合法?例如:

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

As an addendum, is it legal to remove items that have not been iterated over yet? For instance,

作为附录,删除尚未迭代的项目是否合法?例如,

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

采纳答案by Mark

To safely remove from a collection while iterating over it you should use an Iterator.

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

For example:

例如:

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();
}

From the Java Documentation:

Java 文档

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间以任何方式修改列表结构,除了通过迭代器自己的 remove 或 add 方法,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。

Perhaps what is unclear to many novices is the fact that iterating over a list using the for/foreach constructs implicitly creates an iterator which is necessarily inaccessible. This info can be found here

也许许多新手不清楚的是,使用 for/foreach 构造迭代列表会隐式地创建一个必然无法访问的迭代器。这个信息可以在这里找到

回答by Jared Oberhaus

You don't want to do that. It can cause undefined behavior depending on the collection. You want to use an Iteratordirectly. Although the for each construct is syntactic sugar and is really using an iterator, it hides it from your code so you can't access it to call Iterator.remove.

你不想那样做。它可能会导致未定义的行为,具体取决于集合。您想直接使用迭代器。尽管 for each 构造是语法糖并且实际上使用了迭代器,但它对您的代码隐藏了它,因此您无法访问它以调用Iterator.remove.

The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.

如果在迭代正在进行时以除调用此方法以外的任何方式修改了基础集合,则迭代器的行为是未指定的。

Instead write your code:

而是编写您的代码:

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

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

Note that the code calls Iterator.remove, not List.remove.

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

Addendum:

附录:

Even if you are removing an element that has not been iterated over yet, you still don't want to modify the collection and then use the Iterator. It might modify the collection in a way that is surprising and affects future operations on the Iterator.

即使您要删除尚未迭代的元素,您仍然不想修改集合然后使用Iterator. 它可能会以一种令人惊讶的方式修改集合并影响Iterator.

回答by Yishai

The java design of the "enhanced for loop" was to not expose the iterator to code, but the only way to safely remove an item is to access the iterator. So in this case you have to do it old school:

“增强的 for 循环”的 java 设计是不将迭代器暴露给代码,但安全删除项目的唯一方法是访问迭代器。所以在这种情况下,你必须做旧学校:

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

If in the real code the enhanced for loop is really worth it, then you could add the items to a temporary collection and call removeAll on the list after the loop.

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

EDIT (re addendum): No, changing the list in any way outside the iterator.remove() method while iterating will cause problems. The only way around this is to use a CopyOnWriteArrayList, but that is really intended for concurrency issues.

编辑(重新附录):不,在迭代时以任何方式在 iterator.remove() 方法之外更改列表都会导致问题。解决这个问题的唯一方法是使用 CopyOnWriteArrayList,但这实际上是为了解决并发问题。

The cheapest (in terms of lines of code) way to remove duplicates is to dump the list into a LinkedHashSet (and then back into a List if you need). This preserves insertion order while removing duplicates.

删除重复项的最便宜的(就代码行而言)方法是将列表转储到 LinkedHashSet(如果需要,然后返回到列表)。这会在删除重复项的同时保留插入顺序。

回答by sanity

Those saying that you can't safely remove an item from a collection except through the Iterator aren't quite correct, you can do it safely using one of the concurrent collections such as ConcurrentHashMap.

那些说除非通过 Iterator 才能安全地从集合中删除项目的说法不太正确,您可以使用并发集合之一(例如 ConcurrentHashMap)安全地做到这一点。

回答by Chathuranga Withana

Yes you can use the for-each loop, To do that you have to maintain a separate list to hold removing items and then remove that list from names list using removeAll()method,

是的,您可以使用 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

回答by Carsten

  1. Try this 2. and change the condition to "WINTER" and you will wonder:
  1. 试试这个 2. 并将条件更改为“WINTER”,您会想知道:
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");
  }
 }

回答by Serafeim

I didn't know about iterators, however here's what I was doing until today to remove elements from a list inside a loop:

我不知道迭代器,但是直到今天我正在做的事情是从循环内的列表中删除元素:

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

This is always working, and could be used in other languages or structs not supporting iterators.

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

回答by bmcdonald

Make sure this is not code smell. Is it possible to reverse the logic and be 'inclusive' rather than 'exclusive'?

确保这不是代码异味。是否有可能颠倒逻辑并“包容”而不是“排他”?

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

The situation that led me to this page involved old code that looped through a List using indecies to remove elements from the List. I wanted to refactor it to use the foreach style.

导致我进入此页面的情况涉及使用 indecies 循环遍历 List 以从 List 中删除元素的旧代码。我想重构它以使用 foreach 样式。

It looped through an entire list of elements to verify which ones the user had permission to access, and removed the ones that didn't have permission from the list.

它遍历整个元素列表以验证用户有权访问哪些元素,并从列表中删除那些没有权限的元素。

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

To reverse this and not use the remove:

要扭转这一点而不使用删除:

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

When would "remove" be preferred? One consideration is if gien a large list or expensive "add", combined with only a few removed compared to the list size. It might be more efficient to only do a few removes rather than a great many adds. But in my case the situation did not merit such an optimization.

什么时候“删除”是首选?一个考虑因素是如果给一个大列表或昂贵的“添加”,与列表大小相比只删除了几个。仅进行少量删除而不是大量添加可能更有效。但在我的情况下,这种情况不值得进行这样的优化。

回答by song

It's better to use an Iterator when you want to remove element from a list

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

because the source code of remove is

因为remove的源代码是

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

so ,if you remove an element from the list, the list will be restructure ,the other element's index will be changed, this can result something that you want to happened.

所以,如果你从列表中删除一个元素,列表将被重组,另一个元素的索引将被更改,这可能会导致你想要发生的事情。

回答by ktamlyn

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

You clone the list namesand iterate through the clone while you remove from the original list. A bit cleaner than the top answer.

您克隆该列表names并在从原始列表中删除时迭代该克隆。比最佳答案更简洁。