I want to write a custom Throwable that is unchecked.
There are ways to trick the compiler at throw time (e.g. Utility class that re-throws a throwable as unchecked?), which I have implemented thus:
public class CustomThrowable extends Throwable {
public CustomThrowable(String message) {
super(message);
}
@SuppressWarnings("unchecked")
public <T extends Throwable> T unchecked() throws T {
throw (T) this;
}
}
but I'm running into issues at "catch time":
try {
throw new CustomThrowable("foo").unchecked();
}
catch (CustomThrowable t) { } // <- compiler error because the try block does not "throw" a CustomThrowable
Is there a way to simply implement an unchecked Throwable, or is RuntimeException the only way to do that? I had wanted to avoid inheriting from RuntimeException because my throwable is not an exception, but instead a yield instruction.
Update:
Another reason to avoid extending RuntimeException is that my CustomThrowable will get caught by generic catch (Exception ex) { } blocks. Thus, if I want to communicate information from within the stack, each layer needs to potentially be aware that CustomThrowable might come through and explicitly catch-rethrow it; this awareness is a large part of what one is trying to avoid when using the Throwable design.
You can extend Error rather than Throwable. The Java Error class is an unchecked Throwable.
Throwable is a checked error and actually it should be impossible to throw such errors, except of cases when it is a RuntimeException or an Error.
But actually it is possible. Here is an example.
Here is a utility that throws any unchecked exception as checked:
package org.mentallurg;
public class ExceptionUtil {
private static class ThrowableWrapper extends Throwable {
private Throwable throwable;
public ThrowableWrapper(Throwable throwable) {
super();
this.throwable = throwable;
}
@SuppressWarnings("unchecked")
public <T extends Throwable> T throwNested() throws T {
throw (T) throwable;
}
}
private static <T extends Throwable> T throwThis(T throwable) throws T {
throw throwable;
}
public static <T extends Throwable> void throwUnchecked(T throwable) {
new ThrowableWrapper(throwable).throwNested();
}
}
Here is an example of usage:
package org.mentallurg;
public class Test {
private static void doSomething() {
Exception checkedException = new Exception("I am checked exception");
ExceptionUtil.throwUnchecked(checkedException);
}
public static void main(String[] args) {
doSomething();
}
}
Pay attention that there is no throws Throwable clause, neither in main not in doSomething. And there is no compile error. In case of RuntimeException it would be understandable, but not in case of Throwable.
If we execute it, we get following:
Exception in thread "main" java.lang.Exception: I am checked exception
at org.mentallurg.Test.doSomething(Test.java:6)
at org.mentallurg.Test.main(Test.java:11)
How it works?
The most important part is this one:
new ThrowableWrapper(throwable).throwNested();
Actually method throwNested here can throw a Throwable. That's why Java compiler should raise an error and should require that this line of code is either surrounded with try/catch or Throwable clause should be added. But it doesn't. Why? I believe this is a flaw in Java compiler. Some comments on SO in other threads refer to type erasure, but they are incorrect, because type erasure is relevant during the run time, where as we are talking about the compile time.
It is interesting that the decompiled code shows that here a Throwable (not RuntimeException, not Error) will be thrown:
public static <T extends java.lang.Throwable> void throwUnchecked(T);
Code:
0: new #28 // class org/mentallurg/ExceptionUtil$ThrowableWrapper
3: dup
4: aload_0
5: invokespecial #30 // Method org/mentallurg/ExceptionUtil$ThrowableWrapper."<init>":(Ljava/lang/Throwable;)V
8: invokevirtual #32 // Method org/mentallurg/ExceptionUtil$ThrowableWrapper.throwNested:()Ljava/lang/Throwable;
11: pop
12: return
This behaviour is not specific for Throwable. We can replace it with a checked Exception and will get the same result:
public <T extends Exception> T throwNested() throws T {
In all such cases if we replace generics with particular classes, then Java compiler will report an error, which is correct. In cases with generics Java compiler ignores checked exceptions and doesn't reports any error. That's why I believe that this is a bug in Java compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With