前言
人非圣贤,孰能无过,更何况是程序员.所以排错也是我猿的一项基本技能.
一.分析异常种类
排错要先知道有哪些错,先上图,然后一项一项分析!
1.都继承于Throwable
这个类的接口基本决定着java所有异常的行为,比如常用在catch中getMessage(),printStackTrace().其他详细如下
返回类型
方法名称
方法说明
Throwable
fillInStackTrace()
在异常堆栈跟踪中填充。
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;
}
}
}
由上发现,其实这个注解的意义就是抛出所有异常
结束
求各位看客老爷们点个赞再走啊.