迭代时从集合中移除元素

新手上路,请多包涵

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

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

原文由 user1329572 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 679
2 个回答

让我举几个例子和一些替代方案来避免 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 它支持在迭代过程中删除和添加项目。

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

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

使用 JDK >= 8

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

您可以在 Collection 基类中使用新的 removeIf 方法:

 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 = filtered )或使用过滤后的集合 removeAll 找到的元素原始集合(即 books.removeAll(filtered) )。

使用子列表或子集

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

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

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

使用 NavigableSet.subSet 方法或此处提供的任何切片方法,可以通过排序集实现类似的效果。

注意事项:

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

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

原文由 Edwin Dalorzo 发布,翻译遵循 CC BY-SA 4.0 许可协议

老朋友最喜欢的(它仍然有效):

 List<String> list;

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

好处:

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

原文由 Shebla Tsama 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题