Java Sneaky抛出异常,类型擦除

2022-09-01 20:47:53

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

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


}

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

请注意,两者或 main 都没有声明任何已检查的异常,但输出是: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)

此黑客在龙目岛中使用,注释@SneakyThrow,它允许抛出检查的异常而不声明它们。


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


编辑:我知道我们可以在a中插入一个,并且选中/未选中的异常区分是编译时的功能。IntegerList<String>

当从非泛型类型(如)转换为泛型类型(如)时,编译器会生成警告。但是,像上面的代码中那样直接转换为泛型类型并不常见。ListList<XXX>(T) ex

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


答案 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

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

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

答案 2

上面的代码似乎意味着最后我们还可以将Cat类型的值分配给Dog或类似类型的变量。

你必须考虑类的结构。 你正在传递给它。这就像指派 给 不 给 。T extends ThrowableExceptionDogAnimalDogCat

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


选中的异常是编译时功能(如泛型)

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

另外两种方法可以在编译器不知道您正在执行此操作的情况下引发已检查的异常。

Thread.currentThread().stop(throwable);

Unsafe.getUnsafe().throwException(throwable);

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


推荐