王垠十分喜欢Java中的异常声明功能。前段时间,他对Kotlin不支持Checked Exception表示十分失望,写了很长的文章来阐述“Checked Exception的重要性”。王垠的心情我可以理解,异常处理确实很重要,但王垠可能犯了“诉诸动机”的谬误。
因为实践中我没有见过任何库符合王垠理想的Checked Exception用法。
破坏向后兼容性
按王垠的理论,Checked Exception可以在方法签名上标明具体错误原因,但实践中,每次在签名上增加一个新的异常,都会破坏向后兼容性。对于需要复用的框架和库来说,直接就给这个功能判了死刑。
王垠推荐的 FileNotFoundException 这种写法,实践中举步维艰,现在已经越来越罕见了。
比如说你十年前写了个 naive 的 FileOutputStream 构造函数用来打开文件,会抛 FileNotFoundException。现在你发现其实除了 FileNotFoundException 之外,你还需要处理权限不足的问题,但为了保持向后兼容性又不可能在签名中加入权限不足异常。这时 Checked Exception 就十分尴尬了。
这就是为什么现在 Java 7的 Files.newOutputStream 就只标记为 IOException 而不写FileNotFoundException这种具体类型。
类似的异常还有 SQLException 、 ServletException 等,都不写明具体原因了。很多库明明就算抛了异常,还要在异常里面包装一个errorCode,而不用继承的异常,也是这个缘故。
所以王垠在文章中作为反例的“最糟糕的异常处理代码”,即捕获异常的超类而不捕获具体异常,恰好是实践中所有框架,包括 Java 标准库的做法。
Checked Exception和Java标准库自相矛盾
Checked Exception的本意是类型安全。但是,Java标准库一直以一种不类型安全的方式使用异常,比如我们知道 Java 里有一个 Callable ,长成这样:
public interface Callable<V> {
V call() throws Exception;
}你看,这个接口会抛出一切异常,正好就是王垠所说的“最糟糕的异常处理代码”。换句话说,只要你使用Java标准库,Java标准库就会强迫你写出王垠眼中“最糟糕的异常处理代码”。
实际上 Java 语言本身支持泛型异常,比如 Callable 完全可以设计成这样:
public interface Callable<V, E extends Exception> {
V call() throws E;
}不幸的是,看起来 Java 标准库并没有用这种更安全的设计。
Oracle 收购 Sun 以后,似乎 Checked Exception 越来越被标准库所摒弃。Java 7的Files API 用的还是基类IOException,Java 8的Function、BiFunction,压根就不支持 Checked Exception。如果你要用 Stream API 的话,必须手动在 UncheckedIOException 和 IOException 之间包来包去。
把异常不分青红皂白包装成 RuntimeException 的“设计模式”,早已是 Java 程序员大家都知道的秘密,但标准库中加入 UncheckedIOException 可能算是第一次官方承认这个秘密吧。
结论
在几乎所有的 Java 框架和库中,包括 Java 标准库,Checked Exception 都从未以王垠理想的方式使用过。