John said:
Eric, your "performance comparison" is slightly skewed. The
performance characteristics of the two approaches is highly dependent
on the number of iterations and the cost of checking for the
terminating condition. In your comparison the number of iterations
(100) times the cost of checking (i < array.length) is extremely small
compared to the cost of creating and throwing the exception. There are
many scenarios where the opposite is true.
It's usually considered a good idea to quote a few
snippets from the message you're replying to, in order to
give people some context. Keep in mind that propagation
of messages on Usenet is irregular and unsynchronized; it's
likely that some readers see your reply but do not see my
message, and have no idea what you're writing about.
To bring people up to date: A question asked about the
performance cost of using exceptions. I answered with a
simple illustration cribbed from "Effective Java" by Joshua
Bloch, showing two ways to loop through an array: one in
which the index is compared to the array length at each
iteration, and another where the loop simply runs until it
provokes ArrayIndexOutOfBoundsException, which is caught.
I also reported that on my machine the latter was some 15.5
times slower than the former on a 100-element array, and
invited the questioner to make his own measurements.
Now that we're all up-to-date ...
John, I'd be surprised to learn that there are "many
scenarios" where using exceptions for control flow is faster
than using more direct methods like `if'. It might be possible
to concoct such a scenario, but I think it would be difficult.
Consider: if the exception reports the same circumstance that
the `if' or whatever would have tested, the code that decides
to throw the exception must itself make the same test[1]. If
the `if' made the same test, the code could discover the
condition without the overhead of creating and processing the
exception object.
Of course, there's always the possibility of poor design.
Bloch points to Iterator.hasNext() in this regard: the method
is unnecessary in the sense that Iterator.next() will throw
NoSuchElementException when it "runs off the end," so a `try'
block could accomplish all the decision-making that hasNext()
enables.[2] So why does hasNext() exist? Because it's gobs
more efficient and requires less typing, that's why. If you're
confronted with an API designed by someone who hasn't taken this
lesson to heart, an API that offers the "moral equivalent" of
next() without a matching hasNext(), you have little choice but
to use what you're given -- and curse the designer.
Now, observe that next() must somehow make a test that's
equivalent to hasNext(), so the standard idiom for iteration
winds up making the test twice. If that test were expensive
(which it isn't for Iterator, but let's imagine some other
API offering such a pair of methods), it's just possible that
the cost of processing an exception might be smaller than the
cost of making two tests. My informal measurement gives some
rough idea of how expensive a test would need to be before
using exceptions became a practical alternative -- and the
answer is "very expensive indeed," more than the cost of 1500
integer-comparing `if's. I made no claim that this measurement
was highly accurate or was universal, but I'd be comfortable
with saying that the cost of one exception lies somewhere
North of a thousand simple `if' tests.
Yes, it's possible to write an expensive `if', most simply
by calling a few expensive methods inside it. I suggest that
such tests are not typically associated with exceptions: you
simply don't see code that calls BigInteger.probablePrime()
and throws an exception based on the result. (Or if you do,
you're looking at code unlike anything I've seen ...) If you
find "many scenarios" of this sort in the code you work with,
you have my sincerest sympathy.
[1] Or an equivalent test. If the equivalent is cheaper
than the original, one wonders why the `if' would make the
costlier test in the first place.
[2] Or almost. Bloch points out that it's difficult to
distinguish the "expected" exception when next() runs off
the end from an "unexpected" exception thrown because of a
bug somewhere in code executed elsewhere with the loop. A
simple try/catch around the whole loop lumps both together,
whereas hasNext() can easily tell the two conditions apart.