前言

人非圣贤,孰能无过,更何况是程序员.所以排错也是我猿的一项基本技能.

一.分析异常种类

排错要先知道有哪些错,先上图,然后一项一项分析!

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前报错):

  1. 如果try中没有bug,代码会先执行try中的x=1然后执行finally中的x=3,return 3
  2. 如果try中有bug,且被catch到,catch中无异常的话会先执行x=1,然后再执行catch中的x=2,最后执行finally中的x=3,return 3
  3. 如果try中有bug,且被catch到,catch中有异常的话会先执行x=1,然后再执行catch中的x=2,后执行finally中的x=3,最后抛出异常
  4. 如果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...


Java攻城师
451 声望390 粉丝

本人太过于丰富,无法简介