Alexander Mills
Alexander Mills

Reputation: 100290

how to create an immutable iterator?

If I have a Map or a List and get an iterator from it, for example:

var map = new HashMap();
var iterator = map.entrySet().iterator();

if something modifies that map after the fact, will it affect the iterator or is the iterator basically immutable once created?

If it's not immutable, how to create an immutable iterator?

Upvotes: 1

Views: 3674

Answers (4)

shmosel
shmosel

Reputation: 50756

Others have answered how to create an iterator that doesn't permit modification (via Iterator.remove()), but it sounds like you're asking how to create an iterator that's unaffected by changes to the underlying collection; in other words, a snapshot iterator. As a general rule, most Java collections don't support this. If you try modifying the collection after you've obtained an iterator, you'll probably end up with a ConcurrentModificationException.

The solution here depends on the use case. If you're dealing with a collection that's accessed from parallel threads, you'll anyway have to either

  • use a thread-safe collection like ConcurrentHashMap or CopyOnWriteArrayList, which provide snapshot iterators out of the box, or

  • synchronize against writes while reading (including iteration), which precludes the possibility of concurrent modification altogether.

If your code is single-threaded, but the flow requires you to obtain an iterator before modifying a collection, just make a copy of the collection:

var iterator = new HashMap<>(map).entrySet().iterator();
// modify map
// iterate away

Upvotes: 1

AlbertoLopez
AlbertoLopez

Reputation: 101

I had the same question but came up with this solution:

 Iterator iterator = java.util.Collections.enumeration(map.entrySet()).asIterator();

java.util.Collections documentation:

 public static <T> Enumeration<T> enumeration(Collection<T> c)

Returns an enumeration over the specified collection. This provides interoperability with legacy APIs that require an enumeration as input. The iterator returned from a call to Enumeration.asIterator() does not support removal of elements from the specified collection. This is necessary to avoid unintentionally increasing the capabilities of the returned enumeration.

This would be an alternative:

 java.util.Collections.unmodifiableCollection(map.entrySet()).iterator();

Upvotes: 0

Raedwald
Raedwald

Reputation: 48702

The only valid operations for a read-only iterator are increment and dereference (although the Java Iterator combines the two in its next() method). If you have an unmodifiable iterator, that leaves you only dereferencing. The dereferencing can either give you an object reference, or it can be not valid to dereference because it does not refer to a valid position in the collection.

But those are the same semantics as Optional: an Optional can either be empty or have a valid object reference. So, create an Optional from the Iterator and use that as your "unmodifiable iterator":

private Optional<T> unmodifiableNext(Iterator<T> i)
{
   if (i.hasNext()) {
      return Optional.ofNullable(i.next());
   } else {
      return Optional.empty();
   }
}

This has the additional benefit that the Optional is no longer tied to the collection, so the collection can be safely changed without changing which object the Optional refers to.

Upvotes: 1

BlakeTNC
BlakeTNC

Reputation: 971

An iterator instance itself would not generally need the concept of mutability/immutability. However, the collection it is iterating over may be intended to be unmodifiable or immutable. Here is a way to disable the ability of an iterator to change the collection. If you return an instance of this class, then the remove() method of the returned iterator is disabled. The class is used by returning UnmodifiableIterator.create(yourIterator).

import java.util.Iterator;

/**
 * UnmodifiableIterator, A wrapper around an iterator instance that
 * disables the remove method.
 */
public final class UnmodifiableIterator<E> implements Iterator<E> {

    /**
     * iterator, The base iterator.
     */
    private final Iterator<? extends E> iterator;

    private UnmodifiableIterator(final Iterator<? extends E> iterator) {
        this.iterator = iterator;
    }

    public static <E> Iterator<E> create(final Iterator<? extends E> iterator) {
        if (iterator == null) {
            throw new NullPointerException("The iterator can not be null.");
        }
        return new UnmodifiableIterator<>(iterator);
    }

    @Override
    public boolean hasNext() {
        return iterator.hasNext();
    }

    @Override
    public E next() {
        return iterator.next();
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Iterator.remove() is disabled.");
    }

}

Upvotes: 3

Related Questions