【问题标题】:Java SneakyThrow of exceptions, type erasureJava SneakyThrow of exceptions,类型擦除
【发布时间】:2012-12-26 09:40:57
【问题描述】:

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

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 都没有声明任何已检查的异常,但输出为:

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,它允许在不声明的情况下抛出已检查的异常。


我知道这与类型擦除有关,但我不确定是否理解该 hack 的每个部分。


编辑: 我知道我们可以在List&lt;String&gt; 中插入Integer,并且检查/未检查异常的区别是编译时功能。

当从像List 这样的非泛型类型转换为像List&lt;XXX&gt; 这样的泛型类型时,编译器会产生警告。但在上面的代码中,直接转换为像(T) ex 这样的泛型类型并不常见。

如果你愿意,我觉得奇怪的部分是我知道在 JVM 内部 List&lt;Dog&gt;List&lt;Cat&gt; 看起来一样,但上面的代码似乎意味着最后我们也可以分配一个值将 Cat 输入到 Dog 类型的变量或类似的变量中。

【问题讨论】:

标签: java scjp lombok ocpjp


【解决方案1】:

如果你用-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

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

现在只有 编译器 关心已检查和未检查的异常——它根本不是 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...

【讨论】:

  • 查看我的编辑。顺便说一句,如果我记得你来自 C# 单词,那么你为什么建议不要使用 Lombok 的 @SneakyThrow 之类的东西,这有助于不处理检查的异常并在 C# 中做类似的事情
  • @SebastienLorber:因为当我编写 Java 时,我会尝试编写惯用的 Java。顺便说一句,我丝毫看不出 SCJP 认证有什么相关性。您的编辑谈到了分配值 - 在您的代码中分配的值在哪里?
  • 对不起,如果感觉傲慢,只是想说这些东西是认证的主题。我会尝试找到一个相关的代码来告诉你我的意思
  • @SebastienLorber:根据我的经验,获得认证并不意味着有人真正理解深层次的材料。在采访 很多 位 Java 工程师的过程中,我不能说我看到了认证和理解之间的任何关联。因此,我认为这与您的问题并不真正相关。
  • @JonSkeet 你能解释一下编译器逻辑吗?为什么它认为它是RuntimeException?
【解决方案2】:

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

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

编译器根据继承制定了哪些 Throwable 检查哪些不检查的规则。这些是在编译时应用的,并且可能会混淆编译器以允许您抛出检查异常。在运行时这没有影响。


已检查的异常是一种编译时特性(如泛型)

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

另外两种在编译器不知道您正在这样做的情况下引发检查异常的方法。

Thread.currentThread().stop(throwable);

Unsafe.getUnsafe().throwException(throwable);

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

【讨论】:

    【解决方案3】:

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

    @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,因此可以如下使用:

    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 本身不会返回。)

    【讨论】:

    • 答案
    【解决方案4】:

    让我们看看下面的代码:

    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());
      }
    
    
    }
    

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

    首先,任何转换为​​编译器忽略的类型参数。 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 可以轻松地将 ex 类型转换为 Throwable

    这里要注意的第二件事是编译器总是强制我们捕获或传递接收到的已检查异常。 JVM 不进行此类检查。在这里,在我们类型转换之后,理想情况下,SneakyThrow.sneakyThrowInner(ex); 应该被强制处理Throwable 异常,但请记住,这个版本的字节码到达了 JVM。因此,我们以某种方式欺骗了编译器。

    这样做:

    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) {
            try {
                SneakyThrow.sneakyThrow(new Exception());
            }catch (Throwable ex){
                System.out.println("Done Succesfully"); // Added to show everything runs fine
            }
        }
    
    
    }
    

    输出:成功完成

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多