Java SneakyThrow异常,键入擦除

Seb*_*ber 22 java scjp lombok ocpjp

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

public class SneakyThrow {


  public static void sneakyThrow(Throwable ex) {
    SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
  }

  private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
    throw (T) ex;
  }



  public static void main(String[] args) {
    SneakyThrow.sneakyThrow(new Exception());
  }


}
Run Code Online (Sandbox Code Playgroud)

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

请注意,两者都没有sneakyThrow(...)或主要声明任何已检查的异常,但输出为:

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)
Run Code Online (Sandbox Code Playgroud)

本hack在龙目岛的使用,注释@SneakyThrow,允许抛出checked异常没有声明.


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


编辑: 我知道我们可以插入一个Integera List<String>和那个checked/unchecked异常区别是编译时功能.

从非泛型类型List转换为类似于List<XXX>编译器的泛型类型时会产生警告.但是直接转换为泛型类型并不像(T) ex上面的代码那样常见.

如果你愿意,对我来说似乎很奇怪的部分是我理解JVM中的内容List<Dog>并且List<Cat>看起来一样,但上面的代码似乎意味着我们最终还可以将类型Cat的值赋给Dog类型的变量或者类似的东西.

Jon*_*eet 18

如果你用它编译它-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 <T>sneakyThrowInner(Throwable)
1 warning
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • @SebastienLorber:根据我的经验,获得认证并不意味着某人真正*了解*深层次的材料.在采访*批量*Java工程师的过程中,我不能说我已经看到认证与理解之间存在任何关联.因此,我认为这与你的问题无关. (5认同)

Pet*_*rey 8

他上面的代码似乎意味着我们最终还可以将类型为Cat的值分配给类型为Dog的变量或类似的东西.

你必须考虑类的结构.T extends Throwable而你正在Exception转向它.这就好比分配DogAnimalDogCat.

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


已检查的异常是编译时功能(如泛型)

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

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

Thread.currentThread().stop(throwable);

Unsafe.getUnsafe().throwException(throwable);
Run Code Online (Sandbox Code Playgroud)

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


AJN*_*eld 7

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

@SuppressWarnings("unchecked")
static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
    throw (T)t;
}
Run Code Online (Sandbox Code Playgroud)

请参阅“ Java 8 中异常类型推断的一个特殊特性一文

SnekyThrow 的 T 被推断为 RuntimeException。这可以从关于类型推断的语言规范(http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)中遵循

注意:该sneakyThrow函数声明为 return 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;
   }
}
Run Code Online (Sandbox Code Playgroud)

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