(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=1
0: 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;)V
8: new #5 // class com/hammajang/springbootdemo/entity/MyException
11: dup //s 复制异常对象引用
12: invokespecial #6 // Method com/hammajang/springbootdemo/entity/MyException."":()V
15: athrow // 抛出MyException异常
16: astore_1 // 将捕获的异常对象存储在局部变量表中
17: new #7 // class java/lang/RuntimeException
20: dup // 复制异常对象引用
21: aload_1 // 将局部变量中的异常对象加载到操作数栈
22: invokespecial #8 // Method java/lang/RuntimeException."":(Ljava/lang/Throwable;)V
25: athrow // 抛出RuntimeException异常
Exception table:
from to target type
8 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=1
0: 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;)V
8: new #5 // class com/hammajang/springbootdemo/entity/MyException
11: dup // 复制异常对象引用
12: invokespecial #6 // Method com/hammajang/springbootdemo/entity/MyException."":()V
15: athrow // 抛出异常
16: astore_1 // 将异常对象存储在局部变量表中
17: new #7 // class java/lang/RuntimeException
20: dup // 复制异常对象引用
21: aload_1 // 将局部变量表中的异常对象加载到操作数栈
22: invokespecial #8 // Method java/lang/RuntimeException."":(Ljava/lang/Throwable;)V
25: 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;)V
35: aload_2
36: athrow
Exception table:
from to target type
8 16 16 Class com/hammajang/springbootdemo/entity/MyException
8 27 26 any
Exception table:加了finally块后,异常表中多了一条记录,表示从指令8-指令27(try块、catch块)如果抛出了任意类型(any)的异常,都会跳转到指令26(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=1
0: aconst_null
1: astore_1
2: aload_1
3: invokevirtual #2 // Method com/hammajang/springbootdemo/entity/MyException.test:()V
6: new #3 // class com/hammajang/springbootdemo/entity/MyException
9: dup
10: invokespecial #4 // Method com/hammajang/springbootdemo/entity/MyException."":()V
13: astore_1
14: goto 47
17: astore_2
18: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_2
22: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
25: new #3 // class com/hammajang/springbootdemo/entity/MyException
28: dup
29: invokespecial #4 // Method com/hammajang/springbootdemo/entity/MyException."":()V
32: astore_1
33: goto 47
36: astore_3
37: new #3 // class com/hammajang/springbootdemo/entity/MyException
40: dup
41: invokespecial #4 // Method com/hammajang/springbootdemo/entity/MyException."":()V
44: astore_1
45: aload_3
46: athrow
47: return
Exception table:
from to target type
2 6 17 Class java/lang/Exception
2 6 36 any
17 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块)继续执行。