在Java中调用foreach循环中的remove

Mic*_*ick 575 java foreach iterator loops

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

List<String> names = ....
for (String name : names) {
   // Do something
   names.remove(name).
}
Run Code Online (Sandbox Code Playgroud)

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

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

Mar*_*ark 879

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

例如:

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();
}
Run Code Online (Sandbox Code Playgroud)

Java文档:

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

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

  • 请注意,必须先调用i.next()才能调用i.remove():[docs.oracle.com/javase/6/docs/api/java/util/Iterator.html](http://docs. oracle.com/javase/6/docs/api/java/util/Iterator.html#remove()) (37认同)
  • 引用Javadoc for Iterator.remove()"如果在迭代正在进行中以除了调用此方法之外的任何方式修改底层集合,则未指定迭代器的行为." 迭代器充当中间人以安全地执行删除,但允许迭代按预期继续. (20认同)
  • 我很好奇,为什么这被认为是安全的?"迭代者"是中间人吗? (11认同)
  • 但值得注意的是,remove()方法在迭代器上是可选的,如果没有为您的特定集合或JVM实现,则会抛出异常 (7认同)

Jar*_*aus 160

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

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

而是编写你的代码:

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

    String name = it.next();
    // Do something
    it.remove();
}
Run Code Online (Sandbox Code Playgroud)

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

附录:

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

  • 竖起大拇指额外注意*代码调用 Iterator.remove,而不是 List.remove”。我差点错过并使用了 list.remove (3认同)

Yis*_*hai 60

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

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

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

编辑(补遗):不,迭代时在iterator.remove()方法之外以任何方式更改列表都会导致问题.解决这个问题的唯一方法是使用CopyOnWriteArrayList,但这确实是出于并发问题.

最简单的(就代码行而言)删除重复项的方法是将列表转储到LinkedHashSet中(如果需要,然后返回到List中).这样可以在删除重复项时保留插入顺序.


kta*_*lyn 59

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

names从原始列表中删除时克隆列表并遍历克隆.比最佳答案更清洁.

  • 虽然简短而干净,但如果性能/内存使用成为问题,值得注意的是,此解决方案在O(n²)中运行并创建原始列表的副本,这需要内存和操作,具体取决于列表的类型.在LinkedList上使用迭代器可以将复杂性降低到O(n). (12认同)
  • 谨防.对于没有equals和hashCode方法的复合对象,它可能只会失败.但是,标记的答案会安全地删除它. (4认同)
  • 这应该是这里的第一个答案...... (3认同)
  • @SND为什么会是n^2?创建列表的副本是 O(n),因此这也是 O(n)。 (2认同)
  • @FINDarkside O(n ^ 2)并非来自创建副本,而是在对每个元素(以及O(n))调用remove()时遍历ArrayList(O(n))。 (2认同)

Ser*_*eim 27

我不知道迭代器,但是这是我今天要做的事情,从循环中的列表中删除元素:

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

这始终有效,可以用于其他语言或不支持迭代器的结构.

  • 注意:这很正常,因为你在元素上迭代后缀,所以当你删除第i个元素时,其他剩余元素的索引不会改变.如果你从0循环到`size() - 1`,并进行删除,你会看到其他答案中提到的问题.不错的工作! (7认同)
  • 正如附注所示,它应该适用于任何基类列表,但不能移植到更深奥的结构(例如自我排序序列,例如 - 通常,在给定条目的序数的任何位置)可以在列表迭代之间改变). (2认同)
  • 这种基于索引的操作应仅在其元素可在O(1)恒定时间内访问的列表上进行.如果列表不是随机可访问的(不实现RandomAccess),那么您应该使用迭代器,因为这些类型的集合通常需要更长的时间来检索特定索引位置的元素,例如LinkedList. (2认同)
  • 上面方法的优点是您(或至少大多数人)不必通过Google搜索“ java迭代器示例”,而是可以通过内存立即将其编写。 (2认同)

Cha*_*ana 23

是的,您可以使用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
Run Code Online (Sandbox Code Playgroud)

  • 为什么要添加另一个列表的开销? (2认同)
  • 因为`for-each`循环隐藏了`iterator`,所以您不能直接调用`remove()`。因此,为了在迭代过程中从“ for-each”循环中删除项目,必须维护一个单独的列表。该列表将保留对要删除的项目的引用... (2认同)

bmc*_*ald 5

确保这不是代码异味。是否可以颠倒逻辑,做到“包容”而不是“排他”?

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

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

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

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

要扭转这种情况而不使用删除:

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

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