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
可以看到有一个checkcast
到RuntimeException
:
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 本身没有对它们进行验证。它只知道抛出异常。
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
可以看到有一个checkcast
到RuntimeException
:
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 本身没有对它们进行验证。它只知道抛出异常。