Java 中显式 throw 与调用只 throw 异常方法的区别

Java 代码中如果显式的用 throw 关键字抛出异常,那么在该分支中其后的语句不可到达,并且即使对于有返回值的函数也不必写 return  语句了。像下面的代码

以上代码是合法的。要清洁代码的话,最后的 return num + 1  不必写在 else 条件中,这样写只是为了验证抛出异常后不必有返回值。

比如我们想对该代码进行重构,把 throw 语句抽取到一个方法中,以便于在该方法中集中处理错误信息,于是变成了

看起来是把整条  throw 语句置换成的 panic() 方法调用, 咋一看没什么问题,可是上面代码无法编译通过

javac Test.java
Test.java:12: error: missing return statement
        }
        ^
1 error

因为 Java 编译器只看在当前代码行有没有 throw 关键字,才会认定后面的语句会否到达或可省略 return 语句,把 throw  关键字藏到一个方法中(即使该方法直接 throw 异常) 就不能理解了。

这时候保证方法只有一个出口就有成效了,修改后的代码

如此就和显式 throw 异常一样的效果了,但会给阅读代码带来困惑,必须清楚 panic() 方法必定是抛出了异常,而不能在某种逻辑下不抛异常,而正常执行了 if 语句后续的代码。

panic 是借鉴了 Go, Rust 语言中的用法,panic 是 Go 语言的关键字,panic!Rust 的宏,它们都能立即抛出异常。

// Go language
panic("something went wrong!")
// Rust language
panic!("something went wrong!");

panic()panic!() 的语句都不可到达。panic 不是 Java 的关键字,但如果在 Java 中也约定用 panic() 表明此处必抛出异常的,就必须调整后代码中的 if/else 条件语句,以保证 panic() 与 throw 语句有同等效果。

另一个问题是,在显式 throw 后的代码是不可到达的,像以下代码

是无法通过编译的,因为代码行 return num * 2  紧接在 throw 语句之后,不可到达,用 javac 编译得到的错误是

Test.java:9: error: unreachable statement
        return num * 2;
        ^
1 error

而且在 IntelliJ IDEA 中也能标识出来

同样的,把该 throw 语句换成  panic() 方法调用, 改成下面的样子

则可通过编译,虽然实际行为并没有变,语句 return num * 2 同样是永远不会被执行。

而且在 IntelliJ IDEA 中也无法识别出来动态抛出异常后的代码是不可到达的

要让 IntelliJ IDEA 能作一定动态分析的话,需要把 IntelliJ IDEA 的 Editor/Inspections/Java/Probable bugs/Unreachable code 选项勾选上,默认未选择。

现在 IntelliJ IDEA 就会用灰色标识出来 return num * 2 是不可到达的,同时我们可以测试一下,如果把 panic() 函数改成不抛出或只是有条件的抛出异常,则 return num * 2 就会被认为是可到达的(正常色彩显示)。

看相关的介绍说用 IntelliJ jetbrains 的 @Contract 标识为 fail 的方法可让 IntelliJ IDEA 用红色标识 panic() 后的代码不可到达,可实测未达到预期,仍然是只有灰色提示。

最后的总结

  1. 在 IntelliJ IDEA 中识别永远抛异常方法调用后代码不可到达的方式只有勾选上 IntelliJ IDEA 的 Editor/Inspections/Java/Probable bugs/Unreachable code
  2. 确保 panic() 方法只抛出异常,安排好方法的 if/else 的结构,对于有返回值的方法,在调用了 panic() 之后保证也有一个返回出口,以通过编译
  3. 在感觉使用 panic() 模式有些不适的话,考虑回归传统的 throw 异常的方式, 省得影响代码阅读,也太考验 Java IDE 和编译器。比如 myError 是静态引入的一个函数, throw myError(-1, "something went wrong")panic(-1, "something went wrong") 多了几个字符
  4. 至少我本文写到这里就打算直接用 throw myError(...) 的,不想再折腾 panic(...) 了,在未得到 Java 编译器支持的话,这样的 panic(...) 并优雅

本文链接 https://yanbin.blog/java-explicitly-throw-and-call-only-throw-function/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments