首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >异常的Java SneakyThrow,类型擦除

异常的Java SneakyThrow,类型擦除
EN

Stack Overflow用户
提问于 2012-12-26 17:40:57
回答 4查看 5.5K关注 0票数 24

有人能解释一下这段代码吗?

代码语言:javascript
复制
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());
  }


}

这可能看起来很奇怪,但这不会产生强制转换异常,并且允许抛出一个受控异常,而不必在签名中声明它,或者将其包装在一个未受控异常中。

请注意,无论是sneakyThrow(...)还是main都没有声明任何已检查的异常,但输出是:

代码语言:javascript
复制
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,允许抛出检查异常而不声明它们。

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

编辑:我知道我们可以在List<String>中插入一个Integer,而且选中/未选中异常的区别是一个编译时特性。

当从像List这样的非泛型类型转换为像List<XXX>这样的泛型类型时,编译器会产生一个警告。但在上面的代码中,直接转换为泛型类型(如(T) ex )的情况较少。

如果您愿意,对我来说似乎很奇怪的部分是,我知道在JVM中,List<Dog>List<Cat>看起来是一样的,但是上面的代码似乎意味着我们最终也可以将Cat类型的值赋给Dog类型的变量或类似的变量。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-12-26 17:45:40

如果你用-Xlint编译它,你会得到一个警告:

代码语言:javascript
复制
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

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

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

不过,我强烈建议您避免这样做。

在许多情况下,当你使用泛型时,会有一个“真正的”检查,因为某些东西使用了所需的类型--但情况并不总是如此。例如:

代码语言:javascript
复制
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...
票数 19
EN

Stack Overflow用户

发布于 2012-12-26 18:07:45

上面的代码似乎意味着我们最终也可以将Cat类型的值赋给

类型的变量或类似的变量。

您必须考虑类是如何构造的。T extends Throwable,而您正在将Exception传递给它。这类似于将Dog赋值给Animal,而不是将Dog赋值给Cat

编译器有基于继承检查哪些Throwable和不检查Throwable的规则。这些都是在编译时应用的,可能会混淆编译器,使其允许您抛出检查异常。在运行时,这没有任何影响。

检查异常是编译时的特性(就像泛型一样)

顺便说一句,Throwable也是一个检查过的异常。如果你是它的子类,它将被检查,除非它是Error或RuntimeException的子类。

抛出检查异常的另外两种方法,而编译器不知道您正在做这件事。

代码语言:javascript
复制
Thread.currentThread().stop(throwable);

Unsafe.getUnsafe().throwException(throwable);

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

票数 9
EN

Stack Overflow用户

发布于 2016-04-13 05:30:15

从Java8开始,不再需要sneakyThrowInner助手方法。sneakyThrow可以写成:

代码语言:javascript
复制
@SuppressWarnings("unchecked")
static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
    throw (T)t;
}

请参阅"A peculiar feature of exception type inference in Java 8“帖子。

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

注意:声明sneakyThrow函数是为了返回RuntimeException,所以它的使用方法如下:

代码语言:javascript
复制
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本身不会返回。)

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14038649

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档