While writing code for another answer on this site I came across this peculiarity:
static void testSneaky() {
final Exception e = new Exception();
sneaky
With sneakyThrow
, the type T
is a bounded generic type variable without a specific type (because there is no where the type could come from).
With nonSneakyThrow
, the type T
is the same type as the argument, thus in your example, the T
of nonSneakyThrow(e);
is Exception
. As testSneaky()
does not declare a thrown Exception
, an error is shown.
Note that this is a known interference of Generics with checked exceptions.
The T of sneakyThrow
is inferred to be RuntimeException
. 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:
A bound of the form
throws α
is purely informational: it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type.
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:
... Otherwise, if the bound set contains
throws αi
, and the proper upper bounds of αi are, at most,Exception
,Throwable
, andObject
, then Ti =RuntimeException
.
This case applies to sneakyThrow
- the only upper bound is Throwable
, so T
is inferred to be RuntimeException
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's T
has got a lower bound of Exception
(ie T
must be a supertype of Exception
, or Exception
itself), which is a checked exception, due to the type it's being called with, so that T
gets inferred as Exception
.
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 is T=Number
. Although Integer
, Float
etc. could also satisfy the constraint, there's no good reason to choose them over Number
.
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
interface Action<T extends Throwable>
{
void doIt() throws T;
}
<T extends Throwable> void invoke(Action<T> action) throws T
{
action.doIt(); // throws T
}
If we invoke with an empty lambda, what would T
be inferred as?
invoke( ()->{} );
The only constraint on T
is an upper bound Throwable
. 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) -
If E has not been inferred from previous steps, and E is in the throw clause,
and E has an upper constraint E<<X,
if X:>RuntimeException, infer E=RuntimeException
otherwise, infer E=X. (X is an Error or a checked exception)
i.e. if the upper bound is Exception
or Throwable
, choose RuntimeException
as the solution. In this case, there is a good reason to choose a particular subtype of the upper bound.