AFAIK,有两种方法:

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

例如,

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

and

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

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


当前回答

你不能做第二个,因为即使你在迭代器上使用remove()方法,你也会抛出一个异常。

就我个人而言,对于所有Collection实例,我更喜欢第一个,尽管创建新Collection需要额外的时间,但我发现它在其他开发人员编辑时不太容易出错。在某些Collection实现中,Iterator remove()是受支持的,而在其他实现中则不受支持。你可以在Iterator文档中阅读更多内容。

第三种选择是创建一个新的Collection,遍历原始Collection,并将第一个Collection中的所有成员添加到第二个Collection中不允许删除的成员。根据Collection的大小和删除的数量,与第一种方法相比,这种方法可以显著节省内存。

其他回答

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

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

同样,我在上面的例子中使用了“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)或使用过滤后的集合从原始集合中删除所有找到的元素(即books.removeAll(filtered))。

使用子列表或子集

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

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

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

使用NavigableSet排序集也可以实现类似的功能。子集方法,或者这里提供的任何切片方法。

注意事项:

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

The collect and removeAl technique works with any Collection (Collection, List, Set, etc). The ListIterator technique obviously only works with lists, provided that their given ListIterator implementation offers support for add and remove operations. The Iterator approach would work with any type of collection, but it only supports remove operations. With the ListIterator/Iterator approach the obvious advantage is not having to copy anything since we remove as we iterate. So, this is very efficient. The JDK 8 streams example don't actually removed anything, but looked for the desired elements, and then we replaced the original collection reference with the new one, and let the old one be garbage collected. So, we iterate only once over the collection and that would be efficient. In the collect and removeAll approach the disadvantage is that we have to iterate twice. First we iterate in the foor-loop looking for an object that matches our removal criteria, and once we have found it, we ask to remove it from the original collection, which would imply a second iteration work to look for this item in order to remove it. I think it is worth mentioning that the remove method of the Iterator interface is marked as "optional" in Javadocs, which means that there could be Iterator implementations that throw UnsupportedOperationException if we invoke the remove method. As such, I'd say this approach is less safe than others if we cannot guarantee the iterator support for removal of elements.

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

为什么不是这个?

for( int i = 0; i < Foo.size(); i++ )
{
   if( Foo.get(i).equals( some test ) )
   {
      Foo.remove(i);
   }
}

如果它是映射,而不是列表,你可以使用keyset()

你可以看到这个例子;如果我们考虑从列表中移除奇数值:

public static void main(String[] args) {
    Predicate<Integer> isOdd = v -> v % 2 == 0;
    List<Integer> listArr = Arrays.asList(5, 7, 90, 11, 55, 60);
    listArr = listArr.stream().filter(isOdd).collect(Collectors.toList());
    listArr.forEach(System.out::println);
}

我会选择第二个,因为你不需要复制内存,迭代器工作得更快。这样可以节省内存和时间。