Removing items from a collection during iteration

AFAIK there are two approaches:

  • Iterate over a copy of the collection
  • Use actual collection iterator

For example,

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

Are there any reasons to prefer one approach over another (for example, preferring the first approach for the simple reason of readability)?

+80
java collections iteration
May 03 '12 at 13:03
source share
7 answers

Let me give you some examples with some alternatives to avoid ConcurrentModificationException .

Suppose we have the following book collection

 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"))); 

Collection and disposal

Collect all the records that you want to delete in the extended loop, and after the iteration is completed, you will delete all the records found.

 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); 

Suppose the operation you want to do is delete.

If you want to “add”, this approach will also work, but I would suggest that you iterate over another collection to determine which items you want to add to the second collection, and then issue the addAll method at the end.

Using ListIterator

Or you can use ListIterator , which supports the remove / add method during the iteration itself.

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

Again, I used the "remove" method, which seems to imply your question, but you can also use its add method to add new elements during the iteration.

Use JDK 8 streams

Or using the JDK 8 threads, lambdas / closures:

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

In these last two cases, to filter the elements from the collection and reassign the original reference to the filtered collection (i.e. books = filtered ) or use the filtered collection in removeAll find the elements from the original collection (i.e. books.removeAll(filtered) ).

Use a count or a subset

There are other alternatives. If the list is sorted and you want to remove consecutive items, you can create a sublist and then clear it:

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

Since the subselection is maintained by the original list, this will be an effective way to remove this subset of elements.

Something similar can be achieved using sorted sets using the NavigableSet.subSet method or any of the cutting methods suggested there.

Questions:

Which method you use may depend on what you are going to do.

  • The collection and removal method works with any collection (Collection, List, Set, etc.).
  • The ListIterator method works only with lists, provided that their implementation ListIterator offers support for add and delete operations.
  • The Iterator approach will generally work on any collection if you are only going to use the iterator removal method.
  • The ListIterator / Iterator approach has the obvious advantage of not copying anything.
  • Examples of third-party and JDK-8 streams actually do not delete anything, but look for the necessary elements, then you can replace the original link with a new one, and let the old one be garbage collected.
  • In the process of collecting and removing it, the disadvantage is that we must repeat the iteration twice. We iterate over the loop in search of the element, and as soon as we find it, we will ask to remove it from the original list, which would mean a second iterative work to search for this element.
  • I think it's worth mentioning that the remove method of the Iterator interface is marked as optional in Javadocs, which means that there may be implementations of Iterator that can throw an UnsupportedOperationException . Thus, I would say that this approach is less secure than the first.
+160
May 3 '12 at
source share

Is there any reason to prefer one approach to another

The first approach will work, but has obvious overhead for copying the list.

The second approach will not work, because many containers do not allow modification during iteration. Includes ArrayList .

If the only modification is to remove the current element, you can do the second approach using itr.remove() (that is, use the iterator remove() method, not the container). This would be my preferred method for iterators supporting remove() .

+10
May 03 '12 at 1:07 pm
source share

Only the second approach will work. You can change the collection during iteration only with iterator.remove() . All other attempts will ConcurrentModificationException .

+3
May 3 '12 at 1:07
source share

There is a different approach in Java 8. Collection # removeIf

eg:

 List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.removeIf(i -> i > 2); 
+2
04 Oct '17 at 7:19 on
source share

I would choose the second one, since you do not need to make a copy of the memory, and Iterator is faster. This way you save memory and time.

0
May 03 '12 at 13:08
source share

You cannot do the second, because even if you use the remove() method on Iterator , you will get an exception .

Personally, I would prefer the first one for all instances of Collection , despite the additional fake of creating a new Collection , I believe that it is less prone to errors during editing by other developers. Some implementations of Collection support Iterator remove() , while others do not. You can read more in the docs for Iterator .

The third option: create a new Collection , iterate over the source text and add all the elements of the first Collection to the second Collection that cannot be deleted. Depending on the size of the Collection and the number of deletions, this can significantly save memory compared to the first approach.

0
May 03 '12 at 1:12 pm
source share

why not this?

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

And if it's a map, not a list, you can use keyset ()

-one
May 03 '12 at 1:41
source share



All Articles