简体   繁体   中英

Why do I have to deal with Throwable if I invoke getCause() on an Exception

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 a Throwable , 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);
}

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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM