Reputation: 3273
It is designed in Java that I get a Throwable
object if I invoke getCause()
on an Exception
.
I understand that getCause()
is simply inherited from Throwable
and I know that Throwable
can be either an Error
or an Exception
, but a programmer generally should work only on the Exception
level without dealing with Throwable
/Error
classes.
What was the reason in the Java exception hierarchy design to, for example, not include getCause()
in the Exception
class that would return an Exception
object?
Here is an illustration of the inconvenience taken from Java Concurrency In Practice (Brian Goetz):
public class Preloader {
private final FutureTask<ProductInfo> future =
new FutureTask<ProductInfo>(new Callable<ProductInfo>() {
public ProductInfo call() throws DataLoadException {
return loadProductInfo();
}
});
private final Thread thread = new Thread(future);
public void start() { thread.start(); }
public ProductInfo get()
throws DataLoadException, InterruptedException {
try {
return future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof DataLoadException)
throw (DataLoadException) cause;
else
throw launderThrowable(cause);
}
}
}
It is said in the book:
...but also because the cause of the
ExecutionException
is returned as aThrowable
, which is inconvenient to deal with...
And in launderThrowable()
it is dealt with rethrowing Error
right away (because we don't want to deal with it) and returning RuntimeException
:
public static RuntimeException launderThrowable(Throwable t) {
if (t instanceof RuntimeException)
return (RuntimeException) t;
else if (t instanceof Error)
throw (Error) t;
else
throw new IllegalStateException("Not unchecked", t);
}
Upvotes: 2
Views: 1309
Reputation: 34638
First, if you have a type that has a few subtypes, and their behavior is really the same, it makes sense to define the methods at the parent class. Basically, what the designers are saying is: "Throwable
can have a cause, which is also a throwable, and you can get that cause". And you can do that in both Exception
and Error
because they both happen to be throwables.
Now, the Throwable
hierarchy exists since Java 1.0, and generics didn't exist there. Nowadays you might be able to have defined the behavior like this:
class ModernThrowable<T extends ModernThrowable<T>> {
T getCause() {...}
}
And you could define ModernException
as extends ModernThrowable<ModernException>
and then you could get the sort of behavior that you expect.
But this design didn't exist back then, so you get a Throwable
back, and you have to cast it. That's how things used to work and you have to keep backward compatibility.
But actually... as plausible as this may sound, this is not true. At least, it's not the whole truth.
It is perfectly OK to get an Error
as the cause of an Exception
. Take a look at javax.management.RuntimeErrorException
. When you are working with an agent, you might get an Error
, which in these special circumstances, should not cause you to abort the system immediately. So you put it as the cause of this special Exception. And thus, your getCause()
will actually return an Error
from an Exception
.
So if it wasn't designed like this, you would have to go through hoops - have a specialty method just for the cause of this particular exception.
Upvotes: 2
Reputation: 441
Programmers generally work at the Exception level when they are implementing applications. But remember that the JVM, for instance, is also implemented in Java and, in this context, programmers should work at the Throwable/Error level too.
So the question of at which level in the exception hierarchy tree you should work its more of an issue of the domain of the system you're implementing, than an issue of the language design itself. So it makes perfect sense from a language design perspective to provide the getCause method in the Throwable class, which is the root of the hierarchy tree. Since the Exception class inherits this method from its superclass, it's not necessary to provide the same method with a different return type just because in some specific domains you don't/shouldn't work with Throwable instances.
Upvotes: 0
Reputation: 72874
getCause
is a method defined in Throwable
, and simply inherited in Exception
. The cause of a Throwable
is simply a Throwable
(it could be an Exception
or an Error
).
IMO, having a method, say getCauseAsException
, that just returns an Exception
if any as the cause exception is not really useful. You could simply invoke getCause()
and check if it's an instance of Exception
if you're just concerned with Exception
and not Error
s.
Upvotes: 4