Reputation: 100290
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
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
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
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
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