迭代时从集合中移除元素
AFAIK,有两种方法:
- 迭代集合的副本
- 使用实际集合的迭代器
例如,
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() } 
有没有什么理由比其他方式更喜欢一种方法(例如,由于可读性的原因,宁愿select第一种方法)?
 让我举几个例子来避免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循环内删除的所有logging,并在完成迭代后,删除所有find的logging。
 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 ,它在迭代过程中支持remove / add方法。 
 ListIterator<Book> iter = books.listIterator(); while(iter.hasNext()){ if(iter.next().getIsbn().equals(isbn)){ iter.remove(); } } 
 再次,我使用了“删除”方法,这是你的问题似乎暗示,但你也可以使用它的add方法在迭代过程中添加新的元素。 
使用JDK 8stream
或者使用JDK 8stream,lambdas / closures:
 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)find的元素(即books.removeAll(filtered) )) 。 
使用子列表或子集
还有其他的select。 如果列表已sorting,并且想要删除连续的元素,则可以创build一个子列表,然后清除它:
 books.subList(0,5).clear(); 
由于子列表是由原始列表支持的,所以这将是删除元素的子集合的有效方式。
 使用NavigableSet.subSet方法或者其中提供的任何切片方法可以实现类似的操作。 
注意事项:
你使用什么方法可能取决于你打算做什么
- collect和remove方法适用于任何Collection(Collection,List,Set等)。
-   ListIterator方法只适用于列表,只要它们的给定ListIterator实现提供了对添加和删除操作的支持。
-  如果只打算使用迭代器的remove方法,那么一般来说Iterator方法可以用于任何集合。
- 在ListIterator /迭代器方法中,显而易见的好处是不需要复制任何东西。
- 第三方和JDK 8stream示例并没有实际删除任何内容,而是查找所需的元素,然后可以replace原始引用,并将旧引用replace为旧引用。
- 在收集和删除方法的缺点是,我们必须迭代两次。 我们遍历foor-loop寻找一个元素,一旦我们find它,我们要求从原始列表中删除它,这意味着要寻找这个给定的项目的第二个迭代工作。
-  我认为值得一提的是Iterator接口的remove方法在Javadocs中被标记为可选的,这意味着可能有Iterator实现可能抛出UnsupportedOperationException。 因此,我认为这种方法比第一种方法更安全。
有没有什么理由更喜欢一种方法
第一种方法将工作,但复制列表明显的开销。
 第二种方法将不起作用,因为许多容器在迭代期间不允许修改。  这包括ArrayList 。 
 如果唯一的修改是删除当前元素,那么可以使用itr.remove() (也就是使用迭代器的remove()方法,而不是容器的方法itr.remove()来使第二种方法工作。  这将是我支持remove()迭代器的首选方法。 
 只有第二种方法才行。 您可以在迭代过程中仅使用iterator.remove()修改集合。 所有其他尝试将导致ConcurrentModificationException 。 
在Java 8中,还有另一种方法。 collections#removeIf
例如:
 List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.removeIf(i -> i > 2); 
我会select第二个,因为你不必做一个内存的副本,迭代器工作得更快。 所以你节省了内存和时间。
 你不能做第二个,因为即使你在Iterator上使用remove()方法, 你也会得到一个抛出的exception 。 
 就个人而言,我更喜欢所有Collection实例的第一个,尽pipe额外听到创build新的Collection ,我发现在其他开发人员编辑过程中不容易出错。 在一些Collection实现中,Iterator remove()被支持,而另一个则不支持。 你可以在Iterator的文档中阅读更多内容。 
 第三种方法是创build一个新的Collection ,迭代原始数据,并将第一个Collection所有成员添加到第二个Collection ,但不能删除。 根据Collection的大小和删除的数量,与第一种方法相比,这可以显着节省内存。 
为什么不呢?
 for( int i = 0; i < Foo.size(); i++ ) { if( Foo.get(i).equals( some test ) ) { Foo.remove(i); } } 
如果是地图,而不是列表,则可以使用keyset()