While writing code for another answer on this site I came across this peculiarity:
static void testSneaky() {
final Exception e = new Exception();
sneakyThrow(e); //no problems here
nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception
}
@SuppressWarnings("unchecked")
static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
throw (T) t;
}
static <T extends Throwable> void nonSneakyThrow(T t) throws T {
throw t;
}
First, I am quite confused why the sneakyThrow
call is OK to the compiler. What possible type did it infer for T
when there is no mention anywhere of an unchecked exception type?
Second, accepting that this works, why then does the compiler complain on the nonSneakyThrow
call? They seem very much alike.
The T of
sneakyThrow
is inferred to beRuntimeException
. This can be followed from the langauge spec on type inference (http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)Firstly, there's a note in section 18.1.3:
This doesn't affect anything, but it points us to the Resolution section (18.4), which has got more information on inferred exception types with a special case:
This case applies to
sneakyThrow
- the only upper bound isThrowable
, soT
is inferred to beRuntimeException
as per the spec, so it compiles. The body of the method is immaterial - the unchecked cast succeeds at runtime because it doesn't actually happen, leaving a method that can defeat the compile-time checked exception system.nonSneakyThrow
does not compile as that method'sT
has got a lower bound ofException
(ieT
must be a supertype ofException
, orException
itself), which is a checked exception, due to the type it's being called with, so thatT
gets inferred asException
.If type inference produces a single upper bound for a type variable, typically the upper bound is chosen as the solution. For example, if
T<<Number
, the solution isT=Number
. AlthoughInteger
,Float
etc. could also satisfy the constraint, there's no good reason to choose them overNumber
.That was also the case for
throws T
in java 5-7:T<<Throwable => T=Throwable
. (Sneaky throw solutions all had explicit<RuntimeException>
type arguments, otherwise<Throwable>
is inferred.)In java8, with the introduction of lambda, this becomes problematic. Consider this case
If we invoke with an empty lambda, what would
T
be inferred as?The only constraint on
T
is an upper boundThrowable
. In earlier stage of java8,T=Throwable
would be inferred. See this report I filed.But that is pretty silly, to infer
Throwable
, a checked exception, out of an empty block. A solution was proposed in the report (which is apparently adopted by JLS) -i.e. if the upper bound is
Exception
orThrowable
, chooseRuntimeException
as the solution. In this case, there is a good reason to choose a particular subtype of the upper bound.With
sneakyThrow
, the typeT
is a bounded generic type variable without a specific type (because there is no where the type could come from).With
nonSneakyThrow
, the typeT
is the same type as the argument, thus in your example, theT
ofnonSneakyThrow(e);
isException
. AstestSneaky()
does not declare a thrownException
, an error is shown.Note that this is a known interference of Generics with checked exceptions.