前言
Java编译中的异常主要分为两类:
-
受查异常,是指编译器要求必须处理的异常,否则编译不通过。
比如IOException及其子类,ClassNotFoundException、FileNotFoundException等。
-
非受查异常,是指编译器不强制要求处理的异常,可能会在运行时抛出。
比如NullPointerException、ArrayIndexOutOfBoundsException等。
场景
受查异常的设计目的是为了提醒开发者这里会发生异常,必须显示处理这些异常。
比如下面的new String()
方法会抛出IO异常,我们要么使用try catch
处理,要么就抛出异常。
// 抛出异常会使代码变长,好处是能明显看出异常类型
public String utf8ToString(byte[] bytes) throws UnsupportedEncodingException {
return new String(bytes, "UTF-8");
}
牛逼的地方来了,我们可以使用@SneakcyThrows
来代替上面抛出异常的形式:
// 干净清爽
@SneakyThrows
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
所以,@SneakyThrows
可用于在不声明throws的情况下偷偷地抛出已检查的异常。
注意!Lombok生成的代码并不会忽略,包装,替换或以其他方式修改引发的检查异常,该抛出什么还是会抛出什么。它只是骗过了编译器,这样javac就不会报错了。
原理
为什么@SneakyThrows可以骗过编译器? 实际上,编译过后的类长这样:
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw Lombok.sneakyThrow(e);
}
}
我们看一下Lombok.sneakyThrow(e)
方法:
// JVM实际上并不了解或关心 "检查异常 "的概念
// 这个方法所做的只是将抛出一个检查异常的行为隐藏在java编译器中
public static RuntimeException sneakyThrow(Throwable t) {
if (t == null) throw new NullPointerException("t");
return Lombok.<RuntimeException>sneakyThrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
throw (T)t;
}
这个方法的核心逻辑是throw (T)t
,利用泛型将父类型Throwable
转换为子类型RuntimeException
。
虽然事实上它不是RuntimeException
,这样写只是为了骗过javac编译器,由于泛型擦除,实际并没有转换类型,而且JVM压根不关心这个,抛异常就是抛异常,不区分受查/非受查。
总结
@SneakyThrows
不是万金油,应当谨慎使用。
Lombok一直以来都备受争议,有人觉得自动生成代码很方便,有人觉得是一种破坏。
个人认为只要开发团队达成共识,Lombok可以一定程度上提升代码可读性和开发效率。