2

I'm wondering why the following code works:

public static void throwUnchecked(Exception e) {
    TestGenericExceptionCast.<RuntimeException> doThrowUnchecked(e);
}

@SuppressWarnings("unchecked")
private static <T extends Exception> void doThrowUnchecked(Exception toThrow) throws T {
    throw (T) toThrow;
}

public static void main(String[] args) {
    throwUnchecked(new Exception());
}

Why can the exception be cast to RuntimeException via generics?

Got it:

No cast happens at all, because no object is ever assigned to something with type T.

The compilers gets tricked and can't see that it's a checked exception anymore.

BlackEye
  • 655
  • 7
  • 22
  • 1
    erasure... see also http://stackoverflow.com/questions/30759692/throws-x-extends-exception-method-signature/30770099#30770099 – ZhongYu Jun 17 '15 at 14:24

2 Answers2

2

Because RuntimeException extends Exception, which means that the Exception toThrow can be cast to T (i.e. to RuntimeException).

As a side note, you can avoid the @SuppressWarnings by doing:

@SuppressWarnings("unchecked")
private static <T extends Exception> void doThrowUnchecked(T toThrow) throws T {
    throw toThrow;
}

However, having this, you would have to cast the Exception from the throwUnchecked() method to RuntimeException or adjust it to Generic, like this:

public static <T extends Exception> void throwUnchecked(T e) throws T {
    TestGenericExceptionCast.doThrowUnchecked(e);
}

And finally, in the main() method, you would call it like:

public static void main(String[] args) {
    throwUnchecked(new RuntimeException());
}

Furthermore, you can improve this by wrapping the thrown exception to RuntimeException:

public static <T extends Exception> void throwUnchecked(T toThrow) {
    throw new RuntimeException(toThrow);
}

public static void main(String[] args) throws Exception {
    throwUnchecked(new Exception());
}
Konstantin Yovkov
  • 59,030
  • 8
  • 92
  • 140
  • The code you mentioned gives a compile error in throwUnchecked method. I don't get why the relationship "RuntimeException extends Exception" should say anything about casting the other way around. If you try to cast RuntimeException instead of T, you get the expected ClassCastException. – BlackEye Jun 17 '15 at 09:23
  • Have you checked my update on the `throwUnchecked()` method? :) – Konstantin Yovkov Jun 17 '15 at 09:23
  • No, sry. Where is your update on the `throwUnchecked()` method? – BlackEye Jun 17 '15 at 09:27
  • This code still throws ClassCastException when calling it with an exception that is not a RuntimeException (for example `throwUnchecked(new Exception());`). – Eran Jun 17 '15 at 09:28
  • 1
    But now these two methods do nothing useful. You can replace `throwUnchecked(new RuntimeException());` with `throw new RuntimeException()` and it wouldn't change the behavior. – Eran Jun 17 '15 at 09:35
  • `public static void throwUnchecked(T toThrow) { throw new RuntimeException(toThrow); }` is no improment, because now you wrap the originally thrown exception into another one. In my op the exception keeps it's type and only becomes unchecked AND becomse no RuntimeException! (Can't be catched with catch (RuntimeException)). – BlackEye Jun 17 '15 at 10:44
  • This is correct. But personally I would like to keep all my exception to be `Runtime` ones. This makes the code clearer and easy to be customized. – Konstantin Yovkov Jun 17 '15 at 10:46
2

throw (T) toThrow; doesn't really cast toThrow to RuntimeException. After erasure, the type bound <T extends Exception> means that the actual cast that takes place is throw (Exception) toThrow;, which works perfectly fine.

If, however, you change your signature to:

private static <T extends RuntimeException> void doThrowUnchecked(Exception toThrow) throws T

It would still pass compilation (since RuntimeException is a sub-type of Exception), but throwUnchecked(new Exception()); would throw a ClassCastException, since the run-time type of the exception - java.lang.Exception - cannot be cast to java.lang.RuntimeException.

If the purpose of these methods is to convert a checked exception to an unchecked exception, you can delete these methods and replace

throwUnchecked(new Exception());    

with

throw new RuntimeException (new Exception());

This will wrap the checked exception inside an unchecked exception.

Eran
  • 359,724
  • 45
  • 626
  • 694
  • As I mentiod above, wrapping the exception makes several differences. In most cases this will work fine, but my op is a solution to uncheck an exception without wrapping it. But I still don't get the mechanism behind :( – BlackEye Jun 17 '15 at 10:48
  • @BlackEye You can't cast a checked exception type to an unchecked exception type (just like you can't cast an Animal reference to a Dog if it's actually a Cat instance). Wrapping the exception is the only way I can think of. – Eran Jun 17 '15 at 10:52
  • @BlackEye What do you mean by works fine? It passes compilation, but it doesn't cast the passed Exception to a RuntimeException. If you change the code so that it actually casts to a RuntimeException, you'll get a ClassCastException at runtime. – Eran Jun 17 '15 at 11:09
  • "It passes compilation, but it doesn't cast the passed Exception to a RuntimeException." That is what's fine about it. – BlackEye Jun 17 '15 at 12:03
  • @BlackEye So basically, you went into all this trouble just to fool the compiler into letting you throw a checked exception without declaring it in a throws clause. Wouldn't it be simpler to throw RuntimeExceptions in the first place? There's a good reason why unhandled checked exceptions must appear in the throws clause. It is a nice trick though. – Eran Jun 17 '15 at 12:13
  • A colleague of mine sent this code snippet to me and I tried to figure out why it works. I don't remember any case where it would have been better to use this code for our purposes, but maybe the time will come when it is good to know this "hack". My colleagues couldn't explain what is happening there, so I thought this is no common knowledge. Thanks for helping me to understand this behaviour :) – BlackEye Jun 17 '15 at 13:24