Search code examples
javaexceptionperformance-testing

Is throwing an Exception expensive in Java?


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

Solution

  • 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;
            }
        }
    }