JUnit 如何屏蔽已检查的异常?

How does JUnit mask checked exceptions?

JUnit 5 使用此代码屏蔽已检查的异常:

public static RuntimeException throwAsUncheckedException(Throwable t) {
    Preconditions.notNull(t, "Throwable must not be null");
    ExceptionUtils.throwAs(t);

    // Appeasing the compiler: the following line will never be executed.
    return null;
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> void throwAs(Throwable t) throws T {
    throw (T) t;
}

throwAs的调用中,Java如何决定类型变量T的值?

更重要的是,这段代码如何屏蔽已检查的异常?

我认为 T 被推断为 RuntimeException。我通过对 throwAsUncheckedException:

的代码进行以下更改来推断
var o = ExceptionUtils.throwAs(t);

... 并将 throwAs 的声明更改为:

private static <T extends Throwable> T throwAs(Throwable t) throws T

(请注意,我使用 Java 10 中的 var 让编译器在不提供任何进一步信息的情况下推断类型。)

编译后使用javap -c可以看到有一个checkcastRuntimeException:

invokestatic  #2  // Method throwAs:(Ljava/lang/Throwable;)Ljava/lang/Throwable;
checkcast     #3  // class java/lang/RuntimeException
astore_1

这使得 throwAs 可以不声明它抛出任何其他东西 - 它只是调用一个声明它抛出 T 的方法,所以在这种情况下 RuntimeException

JLS section 18 中有更多似是而非的证据,其中包括这一行:

Otherwise, if the bound set contains throws αi, and each proper upper bound of αi is a supertype of RuntimeException, then Ti = RuntimeException.

该部分未提及其他具体异常类型或 Throwable。不幸的是,我发现第 18 节非常难以理解,所以这更像是一个 "yes, that vaguely supports my theory" 而不是很好的证据。

在这种特殊情况下,我认为 throwAsUncheckedException 方法只明确指定类型参数实际上会很好(并且在理解方面更简单):

ExceptionUtils.<RuntimeException>throwAs(t);

这就是 编译器 被修改的方式。它认为只会抛出RuntimeException。抛出的 actual 值是传入的任何值。由于所有正常的类型擦除原因,转换为 T 被忽略...如果您将代码更改为 实际上 转换为 RuntimeException,它将失败任何检查异常。这就是为什么它需要是一个通用方法:包含一个满足编译器的强制转换,而不是在执行时真正强制转换。

JVM 允许这样做,因为据我所知,已检查的异常纯粹是编译器方面的问题,JVM 本身没有对它们进行验证。它只知道抛出异常。