Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What parts of the JLS justify being able to throw checked exceptions as if they were unchecked?

I have recently discovered and blogged about the fact that it is possible to sneak a checked exception through the javac compiler and throw it where it mustn't be thrown. This compiles and runs in Java 6 and 7, throwing a SQLException without throws or catch clause:

public class Test {      // No throws clause here     public static void main(String[] args) {         doThrow(new SQLException());     }      static void doThrow(Exception e) {         Test.<RuntimeException> doThrow0(e);     }      static <E extends Exception> void doThrow0(Exception e) throws E {         throw (E) e;     } } 

The generated bytecode indicates that the JVM doesn't really care about checked / unchecked exceptions:

// Method descriptor #22 (Ljava/lang/Exception;)V // Stack: 1, Locals: 1 static void doThrow(java.lang.Exception e);   0  aload_0 [e]   1  invokestatic Test.doThrow0(java.lang.Exception) : void [25]   4  return     Line numbers:       [pc: 0, line: 11]       [pc: 4, line: 12]     Local variable table:       [pc: 0, pc: 5] local: e index: 0 type: java.lang.Exception  // Method descriptor #22 (Ljava/lang/Exception;)V // Signature: <E:Ljava/lang/Exception;>(Ljava/lang/Exception;)V^TE; // Stack: 1, Locals: 1 static void doThrow0(java.lang.Exception e) throws java.lang.Exception;   0  aload_0 [e]   1  athrow     Line numbers:       [pc: 0, line: 16]     Local variable table:       [pc: 0, pc: 2] local: e index: 0 type: java.lang.Exception 

The JVM accepting this is one thing. But I have some doubts whether Java-the-language should. Which parts of the JLS justify this behaviour? Is it a bug? Or a well-hidden "feature" of the Java language?

My feelings are:

  • doThrow0()'s <E> is bound to RuntimeException in doThrow(). Hence, no throws clause along the lines of JLS §11.2 is needed in doThrow().
  • RuntimeException is assignment-compatible with Exception, hence no cast (which would result in a ClassCastException) is generated by the compiler.
like image 639
Lukas Eder Avatar asked Sep 25 '12 09:09

Lukas Eder


People also ask

How can you tell if an exception is checked or unchecked?

Difference Between Checked and Unchecked Exceptions in JavaA checked exception is caught at compile time whereas a runtime or unchecked exception is, as it states, at runtime. A checked exception must be handled either by re-throwing or with a try catch block, whereas an unchecked isn't required to be handled.

What does it mean for an exception to be unchecked?

An unchecked exception is an exception that occurs at the time of execution. These are also called as Runtime Exceptions. These include programming bugs, such as logic errors or improper use of an API. Runtime exceptions are ignored at the time of compilation.

Which of the following is unchecked exception?

Here are the few unchecked exception classes: NullPointerException. ArrayIndexOutOfBoundsException. ArithmeticException.


1 Answers

All this amounts to exploiting the loophole that an unchecked cast to a generic type is not a compiler error. Your code is explicitly made type-unsafe if it contains such an expression. And since the checking of checked exceptions is strictly a compile-time procedure, nothing will break in the runtime.

The answer from the authors of Generics would most probably be along the lines of "If you are using unchecked casts, it's your problem".

I see something quite positive in your discovery—a break out of the stronghold of checked exceptions. Unfortunately, this can't turn existing checked-exception-poisoned APIs into something more pleasant to use.

How this can help

At a typical layered application project of mine there will be a lot of boilerplate like this:

try {   ... business logic stuff ... }  catch (RuntimeException e) { throw e; }  catch (Exception e) { throw new RuntimeException(e); } 

Why do I do it? Simple: there are no business-value exceptions to catch; any exception is a symptom of a runtime error. The exception must be propagated up the call stack towards the global exception barrier. With Lukas' fine contribution I can now write

try {   ... business logic stuff ...  } catch (Exception e) { throwUnchecked(e); } 

This may not seem like much, but the benefits accumulate after repeating it 100 times throughout the project.

Disclaimer

In my projects there is high discipline regarding exceptions so this is a nice fit for them. This kind of trickery is not something to adopt as a general coding principle. Wrapping the exception is still the only safe option in many cases.

like image 101
Marko Topolnik Avatar answered Sep 23 '22 12:09

Marko Topolnik