Java cannot catch generic exception types due to erasure
catch(FooException<Bar> e) --analogy-> o instanceof List<String>
catch
and instanceof
depend on runtime type information; erasure ruins that.
However, it's a little too harsh to forbid generic declaration of exception types like FooException<T>
. Java could allow it; just require that only raw type and wildcard type are allowed in catch
catch(FooException e) --analogy-> o instanceof List
catch(FooException<?> e) o instanceof List<?>
An argument can be made that, the type of an exception is mostly interested by catch clauses; if we cannot catch generic exception types, there's no point of having them in the first place. OK.
Now, we cannot do this either
catch(X e) // X is a type variable
then why does Java allow X
to be thrown in the first place?
... foo() throws X
As we'll see later, this construct, aided by erasure, could actually subvert the type system.
Well, this feature is quite useful though - at least conceptually. We write generic methods so that we can write template code for unknown input and return types; same logic for throw types! For example
<X extends Exception> void logAndThrow(X ex) throws X
...
throw ex;
...
(IOException e){
logAndThrow(e); // throws IOException
We need to able to throw generically to achieve exception transparency when abstracting a block of code. Another example, say we are tired of writing this kind of boilerplate repeatedly
lock.lock();
try{
CODE
}finally{
lock.unlock();
}
and we want a wrapper method for that, taking in a lambda for the CODE
Util.withLock(lock, ()->{ CODE });
The problem is, CODE could throw any exception type; the original boilerplate throws whatever CODE throws; and we want the writeLock()
expression to do the same, i.e. exception transparency. Solution -
interface Code<X extends Throwable>
{
void run() throws X;
}
<X extends Throwable> void withLock(Lock lock, Code<X> code) throws X
...
code.run(); // throws X
...
withLock(lock, ()->{ throws new FooException(); }); // throws FooException
This only matters for checked exceptions. Unchecked exceptions can propagate freely anyways.
One serious flaw of the throws X
approach is that it doesn't work well with 2 or more exception types.
All right, that's really nice. But we are not seeing any of such usages in new JDK APIs. No functional types of any method parameter can throw any checked exception. If you do Stream.map(x->{ BODY })
, BODY
cannot throw any checked exceptions. They really really hate checked exceptions with lambdas.
Another example is CompletableFuture
, where exception is a central part of the whole concept; yet, no checked exceptions are allowed either in lambda bodies.
If you are a dreamer, you might believe that in some future version of Java, an elegant mechanism of exception transparency for lambda will be invented, nothing like this ugly throws X
hack; therefore we don't need to pollute our APIs with <X>
s for now. Yeah, dream on, man.
So, how much is throws X
used anyways?
Throughout the entire JDK source, the only public API that does that is Optional.orElseThrow()
(and its cousins OptionalInt/Long/Double
). That's it.
(There's a shortcoming with the orElseGet/orElseThrow
design - the decision of branching cannot be done in the lambda body. We could instead design a more general method
T orElse( lambda ) throws X
optional.orElse( ()->{ return x; } );
optional.orElse( ()->{ throw ex; } );
optional.orElse( ()->{ if(..)return x; else throw ex; } );
Besides orElseThrow
, the only other method in JDK that throws X
is the non-public ForkJoinTask.uncheckedThrow()
. It is used for "sneaky throw", i.e. a code can throw a checked exception, without the compiler and runtime knowing about it -
void foo() // no checked exception declared in throws
{
throw sneakyThrow( new IOExcepiton() );
}
here, IOException
is thrown from foo()
at runtime; yet the compiler and runtime failed to prevent it. Erasure is largely to blame.
public static RuntimeException sneakyThrow(Throwable t)
{
throw Util.<RuntimeException>sneakyThrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T
{
throw (T)t;
}