Java基础-异常

在程序中经常会出现各种错误,在Java中可以通过异常进行展现出来。
在处理异常时,常用的关键字有:try、catch、finally、throw、throws;
try:通过会出现异常的代码块可以放入try代码块中,用于监听代码块中的是否会出现异常。
catch:通常用于捕获try代码块中出现的异常,用于发生异常捕获后做相关操作。
finally:用于try代码块执行完毕后,必须执行的地方,比如出现IOException后用于关闭连接,就算出现异常,finally也会执行。
throw:用于程序手动抛出异常。
throws:用于方法后,标识方法可能会出现异常。

在Java中异常分为两大类:Error、Exception。
Error:用来表示编译时和系统错误,出现这种错误时一般比较严重,一般有JVM抛出。
Exception:由程序本身可以处理的异常。

异常又可以分为:可检测异常、非检测异常
可检测异常:正确的程序在运行中,很容易出现的、情理可容的异常状况。编译器在编译代码时会检测该异常。一旦出现该异常,必须进行处理,也就是要么try…catch,要么throw抛出异常到上一层,由上层处理,如果一直上抛,最终会抛出到JVM层,最终导致JVM停止。除RuntimeException及其子类以外都是该异常,典型的如IOException。
非检测异常:这种异常并不会直接被编译器所检测,RuntimeException及其子类和Error。

除去Error,只看Exception分为运行时异常和非运行时异常:
运行时异常:和非检测异常一样,只是排除Error,都是RuntimeException及其子类。
非运行时异常:除RuntimeException及其子类以外的异常。

常见的异常关系继承图如下:

异常流程

编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
System.out.println(ex());
}

public static int ex(){
try {
int i = 1/0;
}catch (Exception e){
// return 1;
throw new RuntimeException("aaa");
}finally {
return 0;
}
}
}

javap -v -l -p -c Main.class反编译后只看部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static int ex();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=0
0: iconst_1
1: iconst_0
2: idiv
3: istore_0
4: iconst_0
5: ireturn
6: astore_0
7: new #6 // class java/lang/RuntimeException
10: dup
11: ldc #7 // String aaa
13: invokespecial #8 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
16: athrow
17: astore_1
18: iconst_0
19: ireturn
Exception table:
from to target type
0 4 6 Class java/lang/Exception
0 4 17 any
6 18 17 any
LineNumberTable:
line 8: 0
line 13: 4
line 9: 6
line 11: 7
line 13: 17
StackMapTable: number_of_entries = 2
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]

上述代码中可以看Exception table中 0~4行如果抛出了java/lang/Exception异常,那么转跳到第6行,0~4如果发生了其他异常转跳到17行,如果6~18行发生其他异常转跳17行。

上述代码中try包含的代码块就是反编译后0~4行,转到第6行为catch异常代码,其中我们是throw一个异常,其实就是创建一个异常对象,然后执行athrow命令,之后去异常表里面查找是否存在异常处理,之后转跳到17行,继续执行然后返回。

这里就可以看出上述代码,并不会抛出RuntimeException,直接返回finlly中的值。

如果修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
System.out.println(ex());
}

public static int ex(){
try {
int i = 1/0;
}catch (Exception e){
throw new RuntimeException("aaa");
}finally {
System.out.println("finally");
}
return 1;
}
}

反编译后得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static int ex();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=0
0: iconst_1
1: iconst_0
2: idiv
3: istore_0
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #5 // String finally
9: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: goto 37
15: astore_0
16: new #8 // class java/lang/RuntimeException
19: dup
20: ldc #9 // String aaa
22: invokespecial #10 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
25: athrow
26: astore_1
27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #5 // String finally
32: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: aload_1
36: athrow
37: iconst_1
38: ireturn
Exception table:
from to target type
0 4 15 Class java/lang/Exception
0 4 26 any
15 27 26 any

反编译后0~4中如果出现异常转跳到15,如果15~27中出现异常转跳26,在15~27为catch中语句,创建RuntimeException对象,然后athrow抛出异常,转跳到26,执行finally语句。之后代码继续执行,可以看到在36处有个athrow,但是在异常表中并没有该处异常处理,所以异常会被抛出。

在java中通过方法调用栈来跟踪线程调用一系列方法调用过程,改栈堆保存了每个调用方法的本地信息(比如方法的局部变量)。每个线程都有一个独立的方法调用栈。每当一个新方法被调用时,Java虚拟机把描述该方法的栈结构置入栈顶,位于栈顶的方法为正在执行的方法。方法执行完毕后该方法会被弹出栈顶。如果在方法中出现异常,且异常表中没有该异常处理,在异常抛出后,虚拟机会从方法栈中弹出该方法,然后在前一个调用方法处查询是否有该异常处理,如果一直没有就会一直追溯到调用栈底部,这时会如下处理:
1、调用异常对象的printStackTrace()方法,打印来自方法调用栈的异常信息。
2、如果该线程不是主线程,那么终止这个线程,其他线程继续正常运行。如果该线程是主线程(即方法调用栈的底部为main()方法),那么整个应用程序被终止。

  • Thread.currentThread().getStackTrace()可以打印当前方法调用栈。

更多异常知识:
Java 异常处理的误区和经验总结
Java 异常处理及其应用
从字节码的角度来看try-catch-finally和return的执行顺序
java学习笔记《面向对象编程》——异常处理