Reputation: 4612
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.
Upvotes: 3
Views: 1466
Reputation: 1263
This approach is fundamentally the same as mentallurg's but it avoids the unnecessary wrapper class.
public final class ThrowUtil {
@SuppressWarnings("unchecked")
private static <T extends Throwable> void throwUnchecked0(Throwable t) throws T {
// because `T` is a generic it will be erased at compile time
// the cast will be replaced with `(Throwable) t` which will always succeed
throw (T) t;
}
public static void throwUnchecked(Throwable t) {
throwUnchecked0(t);
}
private ThrowUtil() {}
}
In your code:
// no throws clause
public void foo() {
ThrowUtil.throwUnchecked(new IllegalAccessException("you are not allowed to call foo"));
}
And for the sake of completeness, here's the generated bytecode for comparison:
private static <T extends java.lang.Throwable> void throwUnchecked0(java.lang.Throwable) throws T;
Code:
0: aload_0
1: athrow
public static void throwUnchecked(java.lang.Throwable);
Code:
0: aload_0
1: invokestatic #1 // Method throwUnchecked0:(Ljava/lang/Throwable;)V
4: return
If throwUnchecked0
is made to take a parameter of type T
instead of any Throwable
then the requirement for either a throws clause or a try/catch statement will be propagated to callers1.
Interestingly, this is very different behaviour from generics in most contexts. For example, if the code is changed so that the internal method returns T
instead of throwing T
and then the public method throws that return value, the type of T
is inferred to be Throwable
and the throws clause requirement will be propagated. Therefore it does seem likely that this is a bug in the Java compiler, albeit one that I would like to see remain since it would still be possible directly in bytecode (and other JVM languages like Kotlin use this extensively) and can be useful in some cases.
The unchecked cast to T
is completely2 safe because T
is erased at compile time and replaced with Throwable
, which will always succeed because the parameter is a Throwable
.
Hiding the real implementation from the public api is not strictly necessary. It simply serves to make clear to readers (and enforce) that the generic type is intended to be completely ignored and omitted from call-sites.
1 The caller could also explicitly specify a type for the generic parameter and cause the requirement for a throws clause to propagate. That's part of the reason for the public wrapper method.
2 Well, only in the sense that it will never fail at runtime. After all, we are throwing unchecked exceptions.
Upvotes: 0
Reputation: 4612
Per Why runtime exception is unchecked exception? (and the unquestionable authority of Jon Skeet ;), it appears that unchecked-exception support is built into the java compiler, and is probably not something I can plug into.
Quoting from Jon's post:
It's explicitly in the specification, section 11.1.1:
RuntimeException and all its subclasses are, collectively, the runtime exception classes.
The unchecked exception classes are the runtime exception classes and the error classes.
The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are all subclasses of Throwable other than RuntimeException and its subclasses and Error and its subclasses.
So yes, the compiler definitely knows about RuntimeException.
I'm still hoping that someone else will come up with a "yes you can" answer, so I'll wait a few more days before closing this question.
Upvotes: 0
Reputation: 5207
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.
Upvotes: 1
Reputation: 1849
You can extend Error
rather than Throwable
. The Java Error
class is an unchecked Throwable
.
Upvotes: 1