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

14ifxucb  于 2023-05-05  发布在  Java
关注(0)|答案(4)|浏览(135)

我想写一个自定义的Throwable是未检查的。
有一些方法可以在抛出时欺骗编译器(例如Utility class that re-throws a throwable as unchecked?),我这样实现了:

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继承,因为我的throwable不是异常,而是yield指令。

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

bmp9r5qi

bmp9r5qi1#

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

bogh5gae

bogh5gae2#

Throwable是一个checked错误,实际上它 * 应该 * 不可能抛出这样的错误,除非它是一个RuntimeException或Error。
但实际上这是可能的。这里有一个例子。
下面是一个将anyunchecked异常作为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();
    }

}

下面是一个使用示例:

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子句,maindoSomething中都没有。而且没有编译错误。在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编译器中的一个bug。

utugiqy6

utugiqy63#

Per Why runtime exception is unchecked exception?(以及Jon Skeet不容置疑的权威;),看起来unchecked-exception支持是内置在java编译器中的,可能不是我可以插入的东西。
引用Jon的文章:
它在规范第11.1.1节中明确指出:
RuntimeException及其所有子类共同构成运行时异常类。
未检查的异常类是运行时异常类和错误类。
检查的异常类是除未检查的异常类之外的所有异常类。也就是说,检查的异常类是Throwable的除RuntimeException及其子类和Error及其子类之外的所有子类。
所以是的,编译器肯定知道RuntimeException。
我仍然希望其他人会给出一个“是的,你可以”的答案,所以我会再等几天再结束这个问题。

vcudknz3

vcudknz34#

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

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语句的要求将传播到callers 1。
有趣的是,在大多数情况下,这与泛型的行为非常不同。例如,如果代码被更改,使得内部方法返回T而不是抛出T,然后公共方法抛出该返回值,则T的类型被推断为Throwable,并且throws子句要求将被传播。因此,这似乎确实是Java编译器中的一个bug,尽管我希望看到它仍然存在,因为它仍然可以直接在字节码中使用(其他JVM语言如Kotlin广泛使用),并且在某些情况下可能很有用。
未经检查的T转换是完全安全的,因为T在编译时被擦除并替换为Throwable,这将始终成功,因为参数是Throwable
对公共API隐藏真实的的实现并不是绝对必要的。它只是为了让读者清楚(并强制)泛型类型是完全忽略的,并从调用站点中省略。
1调用者还可以显式地为泛型参数指定类型,并使throws子句的要求传播。这就是使用公共 Package 方法的部分原因。
2好吧,只是在这个意义上,它永远不会在运行时失败。毕竟,我们正在抛出未经检查的异常。

相关问题