共计 4978 个字符,预计需要花费 13 分钟才能阅读完成。
前言
人非圣贤, 孰能无过, 更何况是程序员. 所以排错也是我猿的一项基本技能.
一. 剖析异样品种
排错要先晓得有哪些错, 先上图, 而后一项一项剖析!
1. 都继承于 Throwable
这个类的接口根本决定着 java 所有异样的行为, 比方罕用在 catch 中 getMessage(),printStackTrace(). 其余具体如下
返回类型
办法名称
办法阐明
Throwable
fillInStackTrace()
在异样堆栈跟踪中填充。
《2020 最新 Java 根底精讲视频教程和学习路线!》
Throwable
getCause()
返回此 throwable 的 cause;如果 cause 不存在或未知,则返回 null。
String
getLocalizedMessage()
创立此 throwable 的本地化形容。
String
getMessage()
返回此 throwable 的具体音讯字符串。
StackTraceElement[]
getStackTrace()
提供编程拜访由 printStackTrace() 输入的堆栈跟踪信息。
Throwable
initCause(Throwable cause)
将此 throwable 的 cause 初始化为指定值。
void
printStackTrace()
将此 throwable 及其追踪输入至规范谬误流。
void
printStackTrace(PrintStream s)
将此 throwable 及其追踪输入到指定的输入流。
void
printStackTrace(PrintWriter s)
将此 throwable 及其追踪输入到指定的 PrintWriter。
void
setStackTrace(StackTraceElement[] stackTrace)
设置将由 getStackTrace() 返回,并由 printStackTrace() 和相干办法输入的堆栈跟踪元素。
String
toString()
返回此 throwable 的简短形容。
2. 继承上分类
-
error: 零碎级别的异样. 该谬误像运行时堆溢出, 属于代码级不可控领域.
- 非受检异样
-
exception: 利用级异样, 属于代码级可控异样. 所以自定义的异样都须要继承这个接口. 同时 exception 也包含如下两类.
- 运行时异样(非受检异样)
- 受检异样
3. 何为受检异样跟非受检异样
- 受检异样:编译器 要查看此类异样, 也就是编辑器会被动爆红提醒减少 try catch.
- 非受检异样:编译器 不查看此类异样, 理论开发时须要开发人员本人逻辑躲避.
二. 异样捕获
妇孺皆知,java 中通过 try catch 来捕获异样, 打到日志里为排错提供根据. 上面对 try catch 的一些场景进行剖析, 照例先上一段案例代码
public class TestTryCatch {public static void main(String[] args) {System.out.println(returnInt());
}
public static int returnInt(){
int x;
try {
x = 1;
return x;
} catch (NullPointerException e) {
x = 2;
return x;
} finally {x = 3;}
}
}
1. 通过字节码查看 returnInt()逻辑
jvm 的 try catch 逻辑是通过 Exception table 条件来实现的, 在 from 到 to 的计数行数中爆出的异样从上向下比对 type 类型, 合乎的则批改程序计数器中的行数到 target 批示的行数.
public static int returnInt();
descriptor: ()I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=0
0: iconst_1 // 取 1 放到栈顶 ----> 是 try 块中的开始
1: istore_0 // 从栈顶将数值放到本地变量(局部变量表第 0 个变量)
2: iload_0 // 保留 x 到 returnValue 中 ----> 是 try 块中的完结
3: istore_1 ----> 是 finally 块中的开始(没报错)
4: iconst_3 // 取 3 放到栈顶
5: istore_0 // 从栈顶将数值放到本地变量(局部变量表第 0 个变量) ----> 是 finally 块中的完结(没报错)
6: iload_1
7: ireturn // 返回后果
8: astore_1 // 给 catch 中定义的 Exception e 赋值 ----> 是 catch 块中的开始
9: iconst_2 //catch 块中的 x =2
10: istore_0
11: iload_0
12: istore_2 // ----> 是 catch 块中的完结
13: iconst_3 //finaly 块中的 x =3 ----> 是 finally 块中的开始(catch 到异样)
14: istore_0
15: iload_2
16: ireturn // 返回后果 ----> 是 finally 块中的完结(catch 到异样)
17: astore_3 // 如果呈现了不属于 java.lang.NullPointerException 及其子类的异样才会走到这里
18: iconst_3 //finaly 块中的 x =3 ----> 是 finally 块中的开始(没 catch 到异样)
19: istore_0
20: aload_3 // 将异样搁置到栈顶,并抛出
21: athrow // ----> 是 finally 块中的完结(没 catch 到异样)
Exception table:
from to target type
0 4 8 Class java/lang/NullPointerException
0 4 17 any
8 13 17 any
2. 列出后果
同时从字节码能够看出有如下后果(都假如在 x 赋值后,return 前报错):
- 如果 try 中没有 bug, 代码会先执行 try 中的 x = 1 而后执行 finally 中的 x =3,return 3
- 如果 try 中有 bug, 且被 catch 到,catch 中无异样的话会先执行 x =1, 而后再执行 catch 中的 x =2, 最初执行 finally 中的 x =3,return 3
- 如果 try 中有 bug, 且被 catch 到,catch 中有异样的话会先执行 x =1, 而后再执行 catch 中的 x =2, 后执行 finally 中的 x =3, 最初抛出异样
- 如果 try 中有 bug, 然而没有被 catch 到则执行 x=1, 而后执行 x = 3, 最初抛出异样
3. 得出结论
从上能够得出结论
- 捕捉异样与抛异样,必须是齐全匹配,或者捕捉异样是抛异样的父类。
- 须要思考到 finally 最终会执行, 须要思考 try 中对象会被批改的问题.
-
不要在 finally 块中应用 return. 通过测试如果 finally 中加了 retrun x; 则字节码中不会呈现 athrow, 而是变为如下 ireturn, 所以应用的时候须要留神 finally 中如果有 return 那么异样将不会被抛出.
...... 23: astore_3 24: iconst_3 25: istore_0 26: iload_0 27: ireturn Exception table: from to target type 0 7 11 Class java/lang/NullPointerException 0 7 23 any 11 19 23 any
在阿里泰山版开发手册中也有阐明.
- try catch 中的逻辑都是不平安的, 都有可能两头跳出, 然而 finally 必定会执行, 所以能够在 finally 中将资源对象、流对象进行敞开
- 字节码中 Exception table 能够看出: 捕捉异样与抛异样,必须是齐全匹配,或者捕捉异样是抛异样的父类, 不然逻辑进不到 catch 当中
三. 异样小工具
1.addSuppressed
例如这样一个场景:try 中呈现了报错, 然而进入到 finally 执行敞开 io 的时候也报了错, 那么后果是办法只会抛出 finally 中报的错.addSuppressed 能够用来解决这个问题, 能够同时将 try 中的谬误跟 finally 中的谬误都抛出. 代码举例如下:
public static int returnInt() throws IOException{
IOException e = null;
int x = 0;
try {
x = 1;
dothing();
return x;
}catch (IOException e1){
e = e1;
x = 2;
return x;
}finally {
x = 3;
try {dothing();
}catch (IOException e2){if(e != null){
// 留神这里
e.addSuppressed(e2);
}else{e = e2;}
}
if(e != null){throw e;}
}
}
2.try-catch-resource
不止各位看官是否感觉下面 addSuppressed 的书写办法特地的繁琐呢,1.7 版本的 try-catch-resource 通过让资源实现 AutoClosable 中的 close 来实现无需手写, 主动调用敞开逻辑的性能; 举例代码:
public class Connection implements AutoCloseable {public void sendData() {System.out.println("正在发送数据");
}
@Override
public void close() throws Exception {System.out.println("正在敞开连贯");
}
}
---------------------------------------------------
public class TryWithResource {public static void main(String[] args) {try (Connection conn = new Connection()) {conn.sendData();
}
catch (Exception e) {e.printStackTrace();
}
}
}
而后将代码放在 idea 外面查看反编译代码
public class TryWithResource {public TryWithResource() { }
public static void main(String[] args) {
try {Connection conn = new Connection();
Throwable var2 = null;
try {conn.sendData();
} catch (Throwable var12) {
var2 = var12;
throw var12;
} finally {if (conn != null) {if (var2 != null) {
try {conn.close();
} catch (Throwable var11) {var2.addSuppressed(var11);
}
} else {conn.close();
}
}
}
} catch (Exception var14) {var14.printStackTrace();
}
}
}
看吧, 原来 try-catch-resource 就是本质上 try-catch-finally 加 addSuppressed 的组合
3.lombok 的 @SneakyThrows
退出有场景不须要精密判断, 而是须要梭哈异样的状况下这个注解能够很不便的帮忙你主动生成 try-catch, 如下代码
import lombok.SneakyThrows;
/**
* @author wuzt
*/
public class TryWithResource {
@SneakyThrows
public static void main(String[] args) {Connection conn = new Connection();
conn.sendData();}
}
应用 idea 反编译后:
public class TryWithResource {public TryWithResource() { }
public static void main(String[] args) {
try {Connection conn = new Connection();
conn.sendData();} catch (Throwable var2) {throw var2;}
}
}
由上发现, 其实这个注解的意义就是抛出所有异样
利用的的场景比拟合乎的像是阿里开发手册中的如下
完结
求各位看客老爷们点个赞再走啊.
链接:https://juejin.cn/post/690186…