Java 中显式 throw 与调用只 throw 异常方法的区别
Java 代码中如果显式的用
以上代码是合法的。要清洁代码的话,最后的
比如我们想对该代码进行重构,把
看起来是把整条
这时候保证方法只有一个出口就有成效了,修改后的代码
如此就和显式
另一个问题是,在显式 throw 后的代码是不可到达的,像以下代码
是无法通过编译的,因为代码行
同样的,把该
则可通过编译,虽然实际行为并没有变,语句
而且在 IntelliJ IDEA 中也无法识别出来动态抛出异常后的代码是不可到达的
要让 IntelliJ IDEA 能作一定动态分析的话,需要把 IntelliJ IDEA 的 Editor/Inspections/Java/Probable bugs/Unreachable code 选项勾选上,默认未选择。
现在 IntelliJ IDEA 就会用灰色标识出来
看相关的介绍说用 IntelliJ jetbrains 的 @Contract 标识为
最后的总结
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
throw 关键字抛出异常,那么在该分支中其后的语句不可到达,并且即使对于有返回值的函数也不必写 return 语句了。像下面的代码1private static int foo(int num) {
2 if (num == 0) {
3 throw new RuntimeException("");
4 } else {
5 return num + 1;
6 }
7}以上代码是合法的。要清洁代码的话,最后的
return num + 1 不必写在 else 条件中,这样写只是为了验证抛出异常后不必有返回值。比如我们想对该代码进行重构,把
throw 语句抽取到一个方法中,以便于在该方法中集中处理错误信息,于是变成了 1private static int foo(int num) {
2 if (num == 0) {
3 panic(-1, "something", "wrong");
4 } else {
5 return num + 1;
6 }
7}
8
9private static void panic(int errorCode, String... message) {
10 throw new RuntimeException("errorCode: %s, message: %s".formatted(errorCode, String.join(" ", message)));
11}看起来是把整条
throw 语句置换成的 panic() 方法调用, 咋一看没什么问题,可是上面代码无法编译通过javac Test.java因为 Java 编译器只看在当前代码行有没有
Test.java:12: error: missing return statement
}
^
1 error
throw 关键字,才会认定后面的语句会否到达或可省略 return 语句,把 throw 关键字藏到一个方法中(即使该方法直接 throw 异常) 就不能理解了。这时候保证方法只有一个出口就有成效了,修改后的代码
1private static int foo(int num) {
2 if (num == 0) {
3 panic(-1, "something", "wrong");
4 }
5 return num + 1;
6}如此就和显式
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 后的代码是不可到达的,像以下代码
1private static int foo(int num) {
2 if (num == 0) {
3 throw new RuntimeException("");
4 return num * 2;
5 }
6 return num + 1;
7}是无法通过编译的,因为代码行
return num * 2 紧接在 throw 语句之后,不可到达,用 javac 编译得到的错误是Test.java:9: error: unreachable statement而且在 IntelliJ IDEA 中也能标识出来
return num * 2;
^
1 error
同样的,把该 throw 语句换成 panic() 方法调用, 改成下面的样子 1private static int foo(int num) {
2 if (num == 0) {
3 panic(-1, "");
4 return num * 2;
5 }
6 return num + 1;
7}
8
9private static void panic(int errorCode, String... message) {
10 throw new RuntimeException("errorCode: %s, message: %s".formatted(errorCode, String.join(" ", message)));
11}则可通过编译,虽然实际行为并没有变,语句
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() 后的代码不可到达,可实测未达到预期,仍然是只有灰色提示。
最后的总结- 在 IntelliJ IDEA 中识别永远抛异常方法调用后代码不可到达的方式只有勾选上 IntelliJ IDEA 的 Editor/Inspections/Java/Probable bugs/Unreachable code
- 确保
panic()方法只抛出异常,安排好方法的 if/else 的结构,对于有返回值的方法,在调用了 panic() 之后保证也有一个返回出口,以通过编译 - 在感觉使用 panic() 模式有些不适的话,考虑回归传统的
throw异常的方式, 省得影响代码阅读,也太考验 Java IDE 和编译器。比如 myError 是静态引入的一个函数,throw myError(-1, "something went wrong")比panic(-1, "something went wrong")多了几个字符 - 至少我本文写到这里就打算直接用
throw myError(...)的,不想再折腾panic(...)了,在未得到 Java 编译器支持的话,这样的panic(...)并优雅
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。