89

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.

0

3 Answers 3

72

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, and Object, 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.

Sign up to request clarification or add additional context in comments.

4 Comments

Small nitpick: In nonSneakyThrow, T must be Exception, not "a supertype of" Exception, because it is exactly the type of the argument declared at compile time at the call site.
@llogiq If I've read the spec correctly, it's got a lower bound of Exception and an upper bound of Throwable, so the least upper bound, which is the resulting inferred type, is Exception.
@llogiq Note that the type of the argument only sets a lower type bound because any supertype of the argument is also acceptable.
the “or Exception itself” phrase might be helpful to the reader but generally, it should be noted that the specification always uses the terms “subtype” and “supertype” in the sense of “including itself”…
19

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.

2 Comments

What is the meaning of X:>RuntimeException in your last example snippet ?
@marsouf - X has a lower bound of RuntimeException.
1

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.

1 Comment

So for sneakyThrow it isn't actually inferred to any specific type, and the "cast" is to such an undefined type? I wonder what actually happens with this.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.