迭代时从集合中删除元素

2022-08-31 05:09:18

AFAIK,有两种方法:

  1. 循环访问集合的副本
  2. 使用实际集合的迭代器

例如

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

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

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


答案 1

让我举几个例子,并附上一些替代方案,以避免.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")));

收集和删除

第一种技术包括收集我们要删除的所有对象(例如,使用增强的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);

这是假设您要执行的操作是“删除”。

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

使用 ListIterator

如果您正在使用列表,则另一种技术包括使用 在迭代本身期间支持删除和添加项的 a。ListIterator

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

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

使用 JDK >= 8

对于那些使用Java 8或更高版本的人来说,您可以使用其他一些技术来利用它。

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

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

或者使用新的流 API:

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

在最后一种情况下,要从集合中筛选出元素,请将原始引用重新分配给已筛选的集合(即 ),或使用筛选后的集合分配给原始集合中找到的元素(即 )。books = filteredremoveAllbooks.removeAll(filtered)

使用子列表或子集

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

books.subList(0,5).clear();

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

使用方法或那里提供的任何切片方法都可以通过排序集来实现类似的目标。NavigableSet.subSet

考虑:

使用哪种方法可能取决于您打算执行的操作

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

答案 2

旧计时器收藏夹(它仍然有效):

List<String> list;

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

好处:

  1. 它只循环访问列表一次
  2. 无需创建额外的对象,或其他不必要的复杂性
  3. 尝试使用已删除项目的索引没有问题,因为...好吧,想想看!