有人能解释一下这段代码吗?
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,允许抛出检查异常而不声明它们。
我知道这与类型擦除有关,但我不确定能理解黑客攻击的每一个部分。
编辑:我知道我们可以在List<String>
中插入一个Integer
,而且选中/未选中异常的区别是一个编译时特性。
当从像List
这样的非泛型类型转换为像List<XXX>
这样的泛型类型时,编译器会产生一个警告。但在上面的代码中,直接转换为泛型类型(如(T) ex
)的情况较少。
如果您愿意,对我来说似乎很奇怪的部分是,我知道在JVM中,List<Dog>
和List<Cat>
看起来是一样的,但是上面的代码似乎意味着我们最终也可以将Cat类型的值赋给Dog类型的变量或类似的变量。
发布于 2012-12-26 17:45:40
如果你用-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...
发布于 2012-12-26 18:07:45
上面的代码似乎意味着我们最终也可以将Cat类型的值赋给
类型的变量或类似的变量。
您必须考虑类是如何构造的。T extends Throwable
,而您正在将Exception
传递给它。这类似于将Dog
赋值给Animal
,而不是将Dog
赋值给Cat
。
编译器有基于继承检查哪些Throwable和不检查Throwable的规则。这些都是在编译时应用的,可能会混淆编译器,使其允许您抛出检查异常。在运行时,这没有任何影响。
检查异常是编译时的特性(就像泛型一样)
顺便说一句,Throwable也是一个检查过的异常。如果你是它的子类,它将被检查,除非它是Error或RuntimeException的子类。
抛出检查异常的另外两种方法,而编译器不知道您正在做这件事。
Thread.currentThread().stop(throwable);
Unsafe.getUnsafe().throwException(throwable);
唯一的区别是两者都使用本机代码。
发布于 2016-04-13 05:30:15
从Java8开始,不再需要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
本身不会返回。)
https://stackoverflow.com/questions/14038649
复制相似问题