异常体系:
(1)所有异常(Exception)、错误(Error)都继承自异常中的基类:Throwable。而异常又可以分为检查异常(Checked Exception)、非检查异常(Unchecked Exception)两大类。
(2)检查异常:在编译期间由编译器检查的异常,编译器确保这些异常在编译期被处理,意味着不能直接使用关键字throw抛出异常,要么使用try、catch处理异常,要么在方法声明上使用throws关键字提醒方法调用者该方法可能会抛出的异常,让方法调用者处理异常。Exception从属子类中,除了RuntimeException类及其从属子类,其它子类都属于这一类型的异常。
直接抛出检查异常:
编译器直接报错,提示该异常需要处理。
使用try、catch捕获异常:
编译器不会报错,因为在catch块捕获了异常,并进行了处理(e.printStackTrace())。
在方法签名上抛出异常:
编译器不会报错,因为在调用该方法时,编译器会强制要求方法调用者使用try、catch捕获异常进行处理,或者继续通过throws关键字往上抛,让更上一层的方法调用者进行处理。
(3)非检查异常:编译器不会在编译期间就检查这类异常,直接抛出这类异常编译器不会报错,只有在程序运行时才可能抛出的异常。RuntimeException及其从属子类、Error及其从属子类都属于这类异常。
直接抛出非检查异常:
编译器不会报错,因为非检查异常只会在程序运行时才会进行相应处理。
(4)如果想自定义检查异常,那么让类直接继承Exception类即可;如果想自定义非检查异常,那么让类直接继承自RuntimeException即可。
自定义检查异常:
自定义非检查异常:
异常处理机制:
(1)try-catch字节码解析
JavaCodes:
public class TestMyException{public static void main(String[] args) {System.out.println("使用try、catch捕获自定义检查异常");try {throw new MyException();} catch (MyException e) {//将自定义检查异常封装成非检查异常throw new RuntimeException(e);}}
}
class MyException extends Exception{}
ByteCodes:
Code:stack=3, locals=2, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #3 // String 使用try、catch处理检查异常5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: new #5 // class com/hammajang/springbootdemo/entity/MyException11: dup //s 复制异常对象引用12: invokespecial #6 // Method com/hammajang/springbootdemo/entity/MyException."<init>":()V15: athrow // 抛出MyException异常16: astore_1 // 将捕获的异常对象存储在局部变量表中17: new #7 // class java/lang/RuntimeException20: dup // 复制异常对象引用21: aload_1 // 将局部变量中的异常对象加载到操作数栈22: invokespecial #8 // Method java/lang/RuntimeException."<init>":(Ljava/lang/Throwable;)V25: athrow // 抛出RuntimeException异常Exception table:from to target type8 16 16 Class com/hammajang/springbootdemo/entity/MyException
Exception table含义:如果执行指令8-指令16(try块)的过程中抛出了MyException类型的异常,则跳转到指令16(finally块)继续执行。
(2)try-catch-finally字节码解析
JavaCodes:
public class TestMyException{public static void main(String[] args) {System.out.println("使用try、catch捕获自定义检查异常");try {throw new MyException();} catch (MyException e) {//将自定义检查异常封装成非检查异常throw new RuntimeException(e);} finally {System.out.println("TestMyException finally code...");}}
}
ByteCodes:
Code:stack=3, locals=3, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #3 // String 使用try、catch捕获自定义检查异常5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: new #5 // class com/hammajang/springbootdemo/entity/MyException11: dup // 复制异常对象引用12: invokespecial #6 // Method com/hammajang/springbootdemo/entity/MyException."<init>":()V15: athrow // 抛出异常16: astore_1 // 将异常对象存储在局部变量表中17: new #7 // class java/lang/RuntimeException20: dup // 复制异常对象引用21: aload_1 // 将局部变量表中的异常对象加载到操作数栈22: invokespecial #8 // Method java/lang/RuntimeException."<init>":(Ljava/lang/Throwable;)V25: athrow // 抛出异常26: astore_2 // 将异常对象存储在局部变量表中27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;30: ldc #9 // String TestMyException finally code...32: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V35: aload_2 36: athrowException table:from to target type8 16 16 Class com/hammajang/springbootdemo/entity/MyException8 27 26 any
Exception table:加了finally块后,异常表中多了一条记录,表示从指令8-指令27(try块、catch块)如果抛出了任意类型(any)的异常,都会跳转到指令26(finally块)继续执行。
finally块解析:
(1)模拟空指针异常
JavaCodes:
public class TestMyException{public static void main(String[] args) {MyException exception = null;try{exception.test();}catch (Exception e){System.out.println(e);}finally {exception = new MyException();}}
}
class MyException extends Exception{public void test(){System.out.println("test method");}
}
ByteCodes:
Code:stack=2, locals=4, args_size=10: aconst_null1: astore_12: aload_13: invokevirtual #2 // Method com/hammajang/springbootdemo/entity/MyException.test:()V6: new #3 // class com/hammajang/springbootdemo/entity/MyException9: dup10: invokespecial #4 // Method com/hammajang/springbootdemo/entity/MyException."<init>":()V13: astore_114: goto 4717: astore_218: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;21: aload_222: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V25: new #3 // class com/hammajang/springbootdemo/entity/MyException28: dup29: invokespecial #4 // Method com/hammajang/springbootdemo/entity/MyException."<init>":()V32: astore_133: goto 4736: astore_337: new #3 // class com/hammajang/springbootdemo/entity/MyException40: dup41: invokespecial #4 // Method com/hammajang/springbootdemo/entity/MyException."<init>":()V44: astore_145: aload_346: athrow47: returnException table:from to target type2 6 17 Class java/lang/Exception2 6 36 any17 25 36 any
有三处地方的指令需要我们注意:
1、指令6、指令9、指令10(try)
2、指令25、指令28、指令29(catch)
3、指令37、指令40、指令41(finally)
这三处地方的指令都执行同一个操作:创建并初始化MyException对象。
从字节码层面我们就可以得知为什么finally块的代码一定会执行了,因为在将.java文件编译成.class字节码文件时,编译器会将finally块的代码放在try块、catch块的末尾。
在Exception table中我们也可以看到,在指令2-指令6(try块)、指令17-25(catch块)执行时,如果抛出了任意(any)类型的异常,就会跳转到指令36(finally块)继续执行。