如何在java中编写未经检查的Throwable



我想编写一个未选中的自定义可抛出对象。

有一些方法可以在抛出时欺骗编译器(例如,将抛出的可抛物重新抛出为未选中的实用程序类?),我已经实现了:

public class CustomThrowable extends Throwable {
public CustomThrowable(String message) {
super(message);
}
@SuppressWarnings("unchecked")
public <T extends Throwable> T unchecked() throws T {
throw (T) this;
}
}

但我在"赶上时间"遇到了问题:

try { 
throw new CustomThrowable("foo").unchecked();
}
catch (CustomThrowable t) { } // <- compiler error because the try block does not "throw" a CustomThrowable

有没有办法简单地实现未经检查的Throwable,还是RuntimeException唯一的方法?我曾想避免从RuntimeException继承,因为我的可抛掷物不是例外,而是一个收益指令。

更新: 避免扩展RuntimeException的另一个原因是我的CustomThrowable会被通用catch (Exception ex) { }块捕获。因此,如果我想从堆栈内传达信息,每一层都需要意识到CustomThrowable可能会通过并显式捕获-重新抛出它;这种意识是人们在使用Throwable设计时试图避免的很大一部分。

您可以扩展Error而不是Throwable。JavaError类是一个未经检查的Throwable

Throwable

是一个已检查的错误,实际上应该不可能抛出这样的错误,除非它是运行时异常或错误。

但实际上这是可能的。下面是一个示例。

这是一个实用程序,它抛出任何未选中的异常:

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();
}
}

下面是一个用法示例:

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();
}
}

请注意,没有throws Throwable条款,无论是main还是doSomething。并且没有编译错误。在RuntimeException的情况下,这是可以理解的,但在Throwable的情况下就不行了.

如果我们执行它,我们会得到以下内容:

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)

它是如何工作的?

最重要的部分是这个:

new ThrowableWrapper(throwable).throwNested();

实际上throwNested这里的方法可以抛出Throwable.这就是为什么Java编译器应该引发错误,并且应该要求这行代码要么用try/catch括起来,要么应该添加Throwable子句。但事实并非如此。为什么?我相信这是Java编译器中的一个缺陷。其他线程中关于 SO 的一些注释提到了类型擦除,但它们是不正确的,因为类型擦除在运行时是相关的,正如我们谈论编译时一样。

有趣的是,反编译代码显示这里将抛出一个Throwable(不是RuntimeException,不是Error):

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

此行为并非特定于Throwable。我们可以用已检查的Exception替换它,并将得到相同的结果:

public <T extends Exception> T throwNested() throws T {

在所有这些情况下,如果我们用特定的类替换泛型,那么 Java 编译器将报告一个错误,这是正确的。在泛型的情况下,Java 编译器会忽略检查的异常,并且不会报告任何错误。这就是为什么我认为这是Java编译器中的一个错误

根据 为什么运行时异常是未经检查的异常?(以及 Jon Skeet ;) 的无可置疑的权威性,似乎 Java 编译器中内置了未经检查的异常支持,并且可能不是我可以插入的内容。

引用乔恩的帖子:

它在规范第 11.1.1 节中明确表示:

RuntimeException 及其所有子类统称为运行时 异常类。

未选中的异常类是运行时异常类和 错误类。

选中的异常类是除未选中的异常类。即,检查的异常类 都是 Throwable 的子类,除了 RuntimeException 及其 子类和错误及其子类。

所以是的,编译器肯定知道RuntimeException。

我仍然希望其他人能想出一个"是的,你可以"的答案,所以我会再等几天再结束这个问题。

这种方法与mentallurg的方法基本相同,但它避免了不必要的包装类。

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() {}
}

在您的代码中:

// no throws clause
public void foo() {
ThrowUtil.throwUnchecked(new IllegalAccessException("you are not allowed to call foo"));
}

为了完整起见,以下是生成的字节码进行比较:

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

如果throwUnchecked0采用类型T的参数而不是任何Throwable则对 throws 子句或 try/catch 语句的要求将传播到调用方1

有趣的是,在大多数情况下,这与泛型的行为非常不同。例如,如果更改了代码,以便内部方法返回T而不是引发T,然后公共方法抛出该返回值,则推断T的类型Throwable,并且将传播 throws 子句要求。因此,这似乎确实可能是 Java 编译器中的一个错误,尽管我希望看到它仍然存在,因为它仍然可以直接在字节码中实现(并且像 Kotlin 这样的其他 JVM 语言广泛使用),并且在某些情况下可能很有用。

未经检查的强制转换为T是完全安全的,因为T在编译时被擦除并替换为Throwable,这将始终成功,因为参数是Throwable

对公共 API 隐藏真正的实现并不是绝对必要的。它只是用于向读者明确(并强制执行)泛型类型旨在从调用站点中完全忽略和省略。


1调用方还可以显式指定泛型参数的类型,并导致传播 throws 子句的要求。这是使用公共包装器方法的部分原因。

2好吧,只是在运行时永远不会失败的意义上。毕竟,我们正在抛出未经检查的异常。

相关内容

  • 没有找到相关文章

最新更新