Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collections.singleton() and forEachRemaining - Java 8

Tags:

java

While working on Collections.singleton() I found that it is not working as expteced. If you see the code below after forEachRemaining code is neither throwing any exception nor returning false on itr.hasNext()

From Java Docs of forEachRemaining

Performs the given action for each remaining element until all elements have been processed

The out put of below code is: true,elem and I am expecting false, NoSuchElementException

public class Test {
        public static void main(String[] args) {
        Collection<String> abc = Collections.singleton("elementsItr");
        final Iterator<String> itr = abc.iterator();
        try {
            itr.forEachRemaining((e) -> {
                throw new RuntimeException();
            });
        } catch (RuntimeException e) {

        }
        System.out.println(itr.hasNext());
        System.out.println(itr.next());

    }
}

Please help me understand this behavior.

like image 292
Sachin Sachdeva Avatar asked Dec 24 '22 16:12

Sachin Sachdeva


2 Answers

Looking at the code: Collections.singleton() returns a SingletonSet. If you call iterator() on a SingletonSet, the resulting iterator is of an anonymous class. The anonymous class overrides forEachRemaining:

public void forEachRemaining(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    if (hasNext) {
        action.accept(e);
        hasNext = false;
    }
}

Since your accept throws an exception, hasNext remains true.

Note that the javadoc doesn't specify what should happen to forEachRemaining if an exception is thrown; thus, it's possible that the next version of the runtime could put hasNext = false above the action.accept(e), leading to a different result. So you can't count on the behavior one way or the other.

like image 130
ajb Avatar answered Jan 18 '23 06:01

ajb


The iterator used by SingletonSet<E> that is the class used to instance the singleton when Collections.singleton(T) is invoked explains this behavior.
The iterator is provided by the static <E> Iterator<E> Collections.singletonIterator(E e) method.

This has a hasNext flag that pass to false only if the consummer has returned without exception :

static <E> Iterator<E> singletonIterator(final E e) {
   ...   
        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            if (hasNext) {
                action.accept(e);
                hasNext = false;
            }
        }
   ...
}

The documentation of the method doesn't explicit this behavior but we could consider it as a safe check to step forward the iterator only it was correctly consumed.
But it gives however a hint about the exception handling :

Exceptions thrown by the action are relayed to the caller.

So if a exception occurs during forEachRemaining(), there is no guarantee on the state of the iterator as the exception is not caught by forEachRemaining() but forwarded to the caller.

like image 20
davidxxx Avatar answered Jan 18 '23 05:01

davidxxx