迭代时从集合中删除元素

use*_*572 196 java iteration collections

AFAIK有两种方法:

  1. 迭代集合的副本
  2. 使用实际集合的迭代器

例如,

List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
    // modify actual fooList
}
Run Code Online (Sandbox Code Playgroud)

Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
    // modify actual fooList using itr.remove()
}
Run Code Online (Sandbox Code Playgroud)

是否有任何理由偏好一种方法而不是另一种方法(例如,由于可读性的简单原因,更喜欢第一种方法)?

Edw*_*rzo 364

让我举几个例子来说明一些替代方案以避免a ConcurrentModificationException.

假设我们有以下书籍集

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));
Run Code Online (Sandbox Code Playgroud)

收集和删除

第一种技术包括收集我们想要删除的所有对象(例如使用增强的for循环),在完成迭代后,我们删除所有找到的对象.

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
    if(book.getIsbn().equals(isbn)){
        found.add(book);
    }
}
books.removeAll(found);
Run Code Online (Sandbox Code Playgroud)

这假设您要执行的操作是"删除".

如果你想"添加"这种方法也可以,但我想你会迭代一个不同的集合来确定你想要添加到第二个集合的元素,然后addAll在最后发布一个方法.

使用ListIterator

如果您正在使用列表,另一种技术包括使用a ListIterator,它支持在迭代过程中删除和添加项目.

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
    if(iter.next().getIsbn().equals(isbn)){
        iter.remove();
    }
}
Run Code Online (Sandbox Code Playgroud)

同样,我在上面的示例中使用了"remove"方法,这是您的问题似乎暗示的,但您也可以使用其add方法在迭代期间添加新元素.

使用JDK 8

对于使用Java 8或高级版本的用户,可以使用其他几种技术来利用它.

您可以removeIfCollection基类中使用新方法:

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));
Run Code Online (Sandbox Code Playgroud)

或者使用新的流API:

ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
                           .filter(b -> b.getIsbn().equals(other))
                           .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

在最后一种情况下,要从books = filtered集合中过滤元素,可以将原始引用重新分配给过滤后的集合(即),或者将过滤后的集合用于removeAll原始集合中的已找到元素(即books.removeAll(filtered)).

使用子列表或子集

还有其他选择.如果列表已排序,并且您要删除连续元素,则可以创建子列表然后清除它:

books.subList(0,5).clear();
Run Code Online (Sandbox Code Playgroud)

由于子列表由原始列表支持,因此这将是删除此元素子集的有效方法.

使用NavigableSet.subSet方法的排序集或其中提供的任何切片方法可以实现类似的东西.

注意事项:

您使用的方法可能取决于您打算做什么

  • 收集和removeAl技术适用于任何集合(集合,列表,集等).
  • ListIterator技术显然只适用于列表,前提是它们的给定ListIterator实现提供了对添加和删除操作的支持.
  • Iterator方法适用于任何类型的集合,但它仅支持删除操作.
  • 使用ListIterator/ Iterator方法,显而易见的优点是不必复制任何东西,因为我们在迭代时删除.所以,这非常有效.
  • JDK 8流示例实际上并没有删除任何内容,而是查找所需的元素,然后我们用新的集合替换原始集合引用,并将旧的集合引用进行垃圾收集.因此,我们只对集合进行一次迭代,这将是有效的.
  • 在收集和removeAll接近方面,缺点是我们必须迭代两次.首先,我们在foor-loop中迭代寻找符合我们删除标准的对象,一旦找到它,我们要求将其从原始集合中删除,这意味着第二次迭代工作要查找此项目以便去掉它.
  • 我认为值得一提的是,Iterator接口的remove方法在Javadocs中被标记为"可选",这意味着如果我们调用remove方法,可能会Iterator抛出实现UnsupportedOperationException.因此,如果我们不能保证迭代器支持删除元素,我会说这种方法不如其他方法安全.

  • 在关于JDK8 Streams的段落中,你提到`removeAll(filtered)`.一个捷径就是`removeIf(b - > b.getIsbn().equals(other))` (5认同)
  • Iterator 和 ListIterator 有什么区别? (2认同)

She*_*ama 14

Old Timer 最爱(它仍然有效):

List<String> list;

for(int i = list.size() - 1; i >= 0; --i) 
{
        if(list.get(i).contains("bad"))
        {
                list.remove(i);
        }
}
Run Code Online (Sandbox Code Playgroud)

好处:

  1. 它只遍历列表一次
  2. 没有创建额外的对象,或其他不需要的复杂性
  3. 尝试使用已删除项目的索引没有问题,因为......好吧,考虑一下!

  • 乍一看你可能会错过它,但秘密是向后遍历列表。这可以防止每次删除都会更改未来潜在删除的索引。 (4认同)
  • 实际上,我更喜欢从头开始迭代列表,删除该项目,然后递减计数器。我认为这具有更好的可读性。所以只需使用:`for (int i = 0; i &lt; list.size(); i++) {...remove(i); 我--;...}` (3认同)
  • 如果有更多的项目需要删除,并且它们一个接一个地排在另一个之后,则此方法将不起作用。要解决此问题,您需要在“list.remove(i);”或向后的内容之后添加“i--”。在此之前查看其他回复,如果您需要在一种情况后删除更多项目,则必须坚持改进这一点 (2认同)

NPE*_*NPE 13

是否有任何理由偏好一种方法而不是另一种方法

第一种方法可行,但复制列表有明显的开销.

第二种方法不起作用,因为许多容器在迭代期间不允许修改.这包括ArrayList.

如果唯一的修改是删除当前元素,则可以通过使用itr.remove()(即使用迭代器remove()方法而不是容器的方法)使第二种方法工作.这将是我支持的迭代器的首选方法remove().

  • @aix我认为值得一提的是,“Iterator”接口的remove方法在Javadoc中被标记为可选,这意味着可能存在可能抛出“UnsupportedOperationException”的Iterator实现。因此,我认为这种方法不如第一种方法安全。根据打算使用的实现,第一种方法可能更合适。 (2认同)

San*_*osh 13

在Java 8中,还有另一种方法.收藏#removeIf

例如:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

list.removeIf(i -> i > 2);
Run Code Online (Sandbox Code Playgroud)

  • @UriLoya,这个问题也可能是一个 [XY 问题](https://meta.stackexchange.com/a/66378),因此这个答案。 (3认同)
  • 这并没有回答OP的问题,这里没有迭代 (2认同)

Ale*_*exR 5

只有第二种方法有效。您只能在迭代期间修改集合iterator.remove()。所有其他尝试都会导致ConcurrentModificationException

  • 第一次尝试迭代副本,这意味着他可以修改原始副本。 (4认同)