迭代时从集合中删除元素

时间:2012-05-03 13:04:00

标签: java collections iteration

AFAIK,有两种方法:

  1. 迭代集合的副本
  2. 使用实际集合的迭代器
  3. 例如,

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

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

9 个答案:

答案 0 :(得分:316)

让我举几个例子来避免使用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&gt; = 8

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

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

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方法或其中提供的任何切片方法,使用排序集可以实现类似的功能。

<强>考虑:

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

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

答案 1 :(得分:13)

  

有没有理由更喜欢一种方法而不是另一种

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

第二种方法不起作用,因为许多容器在迭代期间不允许修改。 This includes ArrayList

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

答案 2 :(得分:12)

在Java 8中,还有另一种方法。 Collection#removeIf

例如:

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

list.removeIf(i -> i > 2);

答案 3 :(得分:8)

最喜欢的旧计时器(仍然有效):

List<String> list;

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

好处:

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

答案 4 :(得分:5)

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

答案 5 :(得分:1)

您无法做到第二步,因为即使您在Iteratoryou'll get an Exception thrown上使用remove()方法也是如此。

就我个人而言,我更喜欢所有Collection个实例的第一个,尽管有另外听到创建新的Collection,我发现在其他开发人员编辑时不太容易出错。在某些Collection实现中,支持Iterator remove(),而另一些则不支持。您可以在Iterator的文档中阅读更多内容。

第三种方法是创建一个新的Collection,对原始内容进行迭代,然后将第一个Collection的所有成员添加到第二个Collection not 删除。与第一种方法相比,这可能会显着节省内存,具体取决于Collection的大小和删除次数。

答案 6 :(得分:0)

我会选择第二个,因为您不必复制内存,Iterator工作得更快。所以你节省了记忆和时间。

答案 7 :(得分:0)

还有一个简单的解决方案,可以“迭代”Collection并删除每个项目。

List<String> list = new ArrayList<>();
//Fill the list

它简单地循环,直到列表为空,并且在每次迭代时,我们用remove(0)删除第一个元素。

while(!list.isEmpty()){
    String s = list.remove(0);
    // do you thing
}

我不相信这与Iterator相比有任何改进,它仍然需要有一个可变列表,但我喜欢这个解决方案的简单性。

答案 8 :(得分:-2)

为什么不呢?

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

如果它是地图而不是列表,则可以使用keyset()

相关问题