Java SneakyThrowexception,键入擦除

有人可以解释这段代码吗?

public class SneakyThrow { public static void sneakyThrow(Throwable ex) { SneakyThrow.sneakyThrowInner(ex); } private static  T sneakyThrowInner(Throwable ex) throws T { throw (T) ex; } public static void main(String[] args) { SneakyThrow.sneakyThrow(new Exception()); } } 

这可能看起来很奇怪,但是这不会产生强制转换exception,并且允许抛出已检查的exception而不必在签名中声明它,或者将其包装在未经检查的exception中。

请注意, sneakyThrow(...)或main都没有声明任何已检查的exception,但输出为:

 Exception in thread "main" java.lang.Exception at com.xxx.SneakyThrow.main(SneakyThrow.java:20) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) 

这个hack在Lombok中使用,带有注释@SneakyThrow,允许抛出已检查的exception而不声明它们。


我知道它与类型擦除有关,但我不确定理解黑客的每个部分。


编辑:我知道我们可以在List插入一个Integer ,并且checked / unchecked exception区别是编译时function。

从像List这样的非generics类型转换为类似List的generics类型时,编译器会生成警告。 但是在上面的代码中直接转换为类似(T) ex的generics类型并不常见。

如果你愿意,那对我来说很奇怪的部分就是我明白在JVM里面一个ListList看起来是一样的,但上面的代码似乎意味着最后我们还可以分配一个类型的值Cat变为Dog类型或类似的变量。

如果使用-Xlint编译它,您将收到警告:

 c:\Users\Jon\Test>javac -Xlint SneakyThrow.java SneakyThrow.java:9: warning: [unchecked] unchecked cast throw (T) ex; ^ required: T found: Throwable where T is a type-variable: T extends Throwable declared in method sneakyThrowInner(Throwable) 1 warning 

这基本上是说“在执行时没有真正检查此强制转换”(由于类型擦除) – 所以编译器不情愿地假设你正在做正确的事情,知道它实际上不会被检查。

现在它只是编译器关心已检查和未检查的exception – 它根本不是JVM的一部分。 所以一旦你通过编译器,你就可以免费回家了。

我强烈建议你尽量避免这样做。

在许多情况下,当您使用generics时会进行“真实”检查,因为某些东西使用了所需的类型 – 但情况并非总是如此。 例如:

 List strings = new ArrayList(); List raw = strings; raw.add(new Object()); // Haha! I've put a non-String in a List! Object x = strings.get(0); // This doesn't need a cast, so no exception... 

他上面的代码似乎意味着最后我们也可以将类型Cat的值赋给类型为Dog的变量或类似的东西。

你必须考虑类的结构。 T extends Throwable ,你将Exception传递给它。 这就像把Animal分配给Animal而不是Dog

编译器具有关于哪些Throwable被检查以及哪些不是基于inheritance的规则。 这些是在编译时应用的,并且可能会混淆编译器以允许您抛出检查exception。 在运行时,这没有任何影响。


已检查的exception是编译时function(如generics)

BTW Throwable也是一个经过检查的例外。 如果你对它进行子类化,它将被检查,除非它是Error或RuntimeException的子类。

另一种抛出已检查exception的方法,而无需编译器知道您正在执行此操作。

 Thread.currentThread().stop(throwable); Unsafe.getUnsafe().throwException(throwable); 

唯一的区别是两者都使用本机代码。

从Java 8开始,不再需要sneakyThrowInner辅助方法。 sneakyThrow可以写成:

 @SuppressWarnings("unchecked") static  RuntimeException sneakyThrow(Throwable t) throws T { throw (T)t; } 

请参阅“ Java 8中的exception类型推断的一个特殊function ”post。

sneakyThrow的T被推断为RuntimeException。 这可以从类型推断的语言规范( http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html )开始。

注意: sneakyThrow函数声明为返回RuntimeException ,因此可以按如下方式使用:

 int example() { if (a_problem_occurred) { throw sneakyThrow(new IOException("An I/O exception occurred")); // Without the throw here, we'd need a return statement! } else { return 42; } } 

通过抛出sneakyThrow返回的RuntimeException ,Java编译器知道此执行路径终止。 (当然, sneakyThrow自己不会回来。)

我们来看看下面的代码:

 public class SneakyThrow { public static void sneakyThrow(Throwable ex) { SneakyThrow.sneakyThrowInner(ex); } private static  T sneakyThrowInner(Throwable ex) throws T { throw (T) ex; } public static void main(String[] args) { SneakyThrow.sneakyThrow(new Exception()); } } 

让我们看看为什么它会像这样。

首先是对编译器忽略的类型参数的任何强制转换。 JVM将直接转换它。 所以, throw (T) ex; 编译器不会检查类型安全性。 但是,当代码到达JVM时,就会发生类型擦除。 因此代码将如下:[注意:实际代码将是字节代码。 以下只是解释擦除的类型。]

 public class SneakyThrow { public static void sneakyThrow(Throwable ex) { SneakyThrow.sneakyThrowInner(ex); // Note : Throwable is checked exception but we don't mention anything here for it } private static Throwable sneakyThrowInner(Throwable ex) throws Throwable { throw (Throwable) ex; } public static void main(String[] args) { SneakyThrow.sneakyThrow(new Exception()); } } 

首先要注意的是,每个东西都会顺利运行,并且不会抛出ClassCastException,因为JVM很容易将Throwable转换类型转换为Throwable

这里要注意的第二件事是编译器总是强迫我们捕获或传递收到的已检查exception。 JVM没有进行此类检查。 在这里,我们输入铸造后,理想情况下, SneakyThrow.sneakyThrowInner(ex); 应该被强制处理Throwableexception,但请记住,此版本的字节代码已到达JVM。 因此,我们以某种方式欺骗了编译器。

这样做:

 public class SneakyThrow { public static void sneakyThrow(Throwable ex) { SneakyThrow.sneakyThrowInner(ex); } private static  T sneakyThrowInner(Throwable ex) throws T { throw (T) ex; } public static void main(String[] args) { try { SneakyThrow.sneakyThrow(new Exception()); }catch (Throwable ex){ System.out.println("Done Succesfully"); // Added to show everything runs fine } } } 

输出:成功完成