4

I have some code that produces an iterator, and uses NoSuchElementException to signal when whatever the source of the iterator is, is exhausted. When profiling this code, I find that 99% of its time is being spent in method java.util.NoSuchElementException.<init>.

I know that exceptions are expensive in C++, but until now I thought that they were not so bad in Java.

It's bad practice to use exceptions for flow control. It was some time ago that I wrote this, so I cannot recall why I did it this particular way...

Is throwing an exception in Java an expensive operation, in terms of the time it takes?


Here is the code:

public abstract class SequenceIterator<E> implements Iterator<E>
{
    /** Caches the next lazily generated solution, when it has already been asked for by {@link #hasNext}. */
    private E nextSolution = null;

    /** Used to indicate that the sequence has been exhausted. */
    private boolean searchExhausted = false;

    /**
     * Generates the next element in the sequence.
     *
     * @return The next element from the sequence if one is available, or <tt>null</tt> if the sequence is complete.
     */
    public abstract E nextInSequence();

    /**
     * Checks if a sequnce has more elements, caching any generated as a result of the check.
     *
     * @return <tt>true</tt> if there are more elements, <tt>false</tt> if not.
     */
    public boolean hasNext()
    {
        boolean hasNext;

        try
        {
            nextInternal();
            hasNext = true;
        }
        catch (NoSuchElementException e)
        {
            // Exception noted so can be ignored, no such element means no more elements, so 'hasNext' is false.
            e = null;

            hasNext = false;
        }

        return hasNext;
    }

    /**
     * Gets the next element from the sequence if one is available. The difference between this method and
     * {@link #nextInSequence} is that this method consumes any cached solution, so subsequent calls advance onto
     * subsequent solutions.
     *
     * @return The next solution from the search space if one is available.
     *
     * @throws NoSuchElementException If no solutions are available.
     */
    public E next()
    {
        // Consume the next element in the sequence, if one is available.
        E result = nextInternal();
        nextSolution = null;

        return result;
    }

    /**
     * Removes from the underlying collection the last element returned by the iterator (optional operation). This
     * method can be called only once per call to <tt>next</tt>. The behavior of an iterator is unspecified if the
     * underlying collection is modified while the iteration is in progress in any way other than by calling this
     * method.
     *
     * @throws UnsupportedOperationException The <tt>remove</tt> operation is not generally supported by lazy sequences.
     */
    public void remove()
    {
        throw new UnsupportedOperationException("Lazy sequences, in general, do not support removal.");
    }

    /**
     * Gets the next element from the sequence, the cached one if one has already been generated, or creating and
     * caching a new one if not. If the cached element from a previous call has not been consumed, then subsequent calls
     * to this method will not advance the iterator.
     *
     * @return The next solution from the search space if one is available.
     *
     * @throws NoSuchElementException If no solutions are available.
     */
    private E nextInternal()
    {
        // Check if the search space is already known to be empty.
        if (searchExhausted)
        {
            throw new NoSuchElementException("Sequence exhausted.");
        }

        // Check if the next soluation has already been cached, because of a call to hasNext.
        if (nextSolution != null)
        {
            return nextSolution;
        }

        // Otherwise, generate the next solution, if possible.
        nextSolution = nextInSequence();

        // Check if the solution was null, which indicates that the search space is exhausted.
        if (nextSolution == null)
        {
            // Raise a no such element exception to signal the iterator cannot continue.
            throw new NoSuchElementException("Seqeuence exhausted.");
        }
        else
        {
            return nextSolution;
        }
    }
}
user2800708
  • 1,890
  • 2
  • 18
  • 31
  • 2
    You say "the Iterator interface does not provide a way of returning a special value to indicate "no more values left"." - what's wrong with `Iterator#hasNext()`? – JonK Sep 28 '15 at 13:49
  • _the Iterator interface does not provide a way of returning a special value to indicate "no more values left"_... Really? how about [hasNext()](http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html#hasNext()) method? – drgPP Sep 28 '15 at 13:50
  • 1
    I'm proposing this question be marked as a duplicate of [How expensive are Exceptions](http://stackoverflow.com/questions/567579/how-expensive-are-exceptions) which in turn is already a duplicate of [How slow are Java exceptions?](http://stackoverflow.com/questions/299068/how-slow-are-java-exceptions) – CubeJockey Sep 28 '15 at 13:50
  • See the fourth argument in [this constructor of `Throwable`](https://docs.oracle.com/javase/8/docs/api/java/lang/Exception.html#Exception-java.lang.String-java.lang.Throwable-boolean-boolean-). This can be used to improve performance of *custom* exception classes at the cost of debugging information. –  Sep 28 '15 at 13:50
  • About the hasNext() method - let me just post up the code so you can take a look. I need to re-look at it myself, as its something I wrote many years ago. – user2800708 Sep 28 '15 at 13:51

2 Answers2

4

Its bad practice to use exceptions for flow control, but there were other reasons that I was in some way compelled to do this - the Iterator interface does not provide a way of returning a special value to indicate "no more values left".

https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html#hasNext--

Is throwing an Exception in Java an expensive operation, in terms of the time it takes?

AFAIK, especially creating the stack trace for an exception is expensive. I'm not 100% sure if this happens in the constructor or when the exception gets thrown, but the following answer indicates it happens in the constructor: https://stackoverflow.com/a/8024032/506855

In any case, as you mentioned yourself, you shouldn't use exceptions for control flow, if you can avoid it. And I think it should be possible to refactor the code you provided to avoid that.

Community
  • 1
  • 1
Puce
  • 37,247
  • 13
  • 80
  • 152
  • 2
    I think it is likely to solve the OPs problem, even if it doesn't answer the question being asked. It seems the cause of the question is derivative of the OP overlooking the `.hasNext()` method. – Blake Yarbrough Sep 28 '15 at 13:52
  • Please see the code I added to the question. I have not overlooked .hasNext(). – user2800708 Sep 28 '15 at 13:53
  • 1
    @user2800708 In the Java implementation of the `hasNext()` method exception logic is not used, I think you have made it less efficient by overriding the functionality. The exception only occurs when you call `.next()` while `.hasNext()` is `false`. See: http://stackoverflow.com/questions/17099782/java-listiterator-and-hasnext – Blake Yarbrough Sep 28 '15 at 13:58
  • I would remove the `nextInternal` method completely and integrate it into `next()`. That would force the refactoring of `hasNext()` to something sensible. – Kayaman Sep 28 '15 at 14:06
2

The comments already mentioned that Exceptions are no replacement for control flow. But you don't need to use Exceptions since you can introduce state in your Iterator class which holds all necessary information to implement hasNext() and next() with a common helper method.

For instance:

public abstract class SequenceIterator<E> implements Iterator<E> {
    private boolean searchExhausted = false;
    private E next;
    private boolean hasNext;
    private boolean nextInitialized;

    public abstract E nextInSequence();

    @Override public boolean hasNext() {
        if (!nextInitialized)
            initNext();
        return hasNext;
    }

    @Override public E next() {
        if (!nextInitialized)
            initNext();
        if (!hasNext())
            throw new NoSuchElementException();
        nextInitialized = false;
        return next;
    }

    private void initNext() {
        hasNext = false;
        nextInitialized = true;

        if (!searchExhausted) {
            next = nextInSequence();
            hasNext = next != null;
        }
    }
}
wero
  • 32,544
  • 3
  • 59
  • 84
  • Thanks for taking the time to rewrite my code for me - I had already come to the same conclusion and a similar solution. As I say, it was something I wrote some time ago. – user2800708 Sep 28 '15 at 14:24
  • Here is a similar post from code review for reference http://codereview.stackexchange.com/questions/35626/iterator-implementation – Blake Yarbrough Sep 28 '15 at 14:27