不想写throws? 使用@SneakcyThrows偷偷抛出异常

1,580 阅读2分钟

前言

Java编译中的异常主要分为两类:

  1. 受查异常,是指编译器要求必须处理的异常,否则编译不通过。

    比如IOException及其子类,ClassNotFoundException、FileNotFoundException等。

  2. 非受查异常,是指编译器不强制要求处理的异常,可能会在运行时抛出。

    比如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可以一定程度上提升代码可读性和开发效率。