0 前言
关于java
字节码指令的含义,可以参考这篇博客:Java字节码指令含义解释与指令查询
1 try…catch…finally
1.1 catch
1.1.1 catch单个异常
查看相应的java
代码:
public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException e){
i = 30;
}
}
}
接下来通过javap -v xxx.class
命令,来查看该段源码的字节码反编译之后的信息:
{ //源代码对应的字节码指令
public com.basic.FinallyDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: bipush 10 //将10压入栈顶
2: istore_1 //将10存储到slot 1中,即10 -> i
3: bipush 20 //将20压入栈顶, try-----开始
5: istore_1 //将20存储到slot 1中,即20 -> i try------结束
6: goto 13 //这里没有判断条件,但是有一个goto指令,结合异常表一起执行
9: astore_2 //存储异常信息到slot 2,即 异常信息 -> e
10: bipush 30 //将30压入栈顶,
12: istore_1 //将30存储到slot 1中,即 30 -> i
13: return //返回栈顶元素
Exception table: //被try..catch包裹的代码,会产生一个异常表
from to target type
3 6 9 Class java/lang/NegativeArraySizeException //监测try中的代码,如果发生异常,跳转到第9行
LineNumberTable:
line 5: 0
line 7: 3
line 10: 6
line 8: 9
line 9: 10
line 11: 13
LocalVariableTable:
Start Length Slot Name Signature
10 3 2 e Ljava/lang/NegativeArraySizeException;
0 14 0 args [Ljava/lang/String;
3 11 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/NegativeArraySizeException ]
frame_type = 3 /* same */
}
SourceFile: "FinallyDemo.java"
从反编译之后的字节码信息可以看到,之所以能执行catch中的代码,是因为有一个异常表(exception table)来存放try
和catch
的执行的起始位置,当检测到try
中有异常发生时,跳转到catch
中执行。
1.1.2 catch多个异常
先看java
源代码:
import java.nio.file.FileSystemAlreadyExistsException;
public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException e){
i = 30;
}catch (NullPointerException e){
i = 40;
}catch (FileSystemAlreadyExistsException e){
i = 50;
}
System.out.println(i);
}
}
源代码对应的字节码指令:
{
public com.basic.FinallyDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10 //定义变量i,将10压入栈顶
2: istore_1
3: bipush 20 //try------开始
5: istore_1 //try------结束
6: goto 27
9: astore_2 //catch1-----开始
10: bipush 30
12: istore_1 //catch2----结束
13: goto 27
16: astore_2 //catch2-----开始
17: bipush 40
19: istore_1 //catch2-----结束
20: goto 27
23: astore_2 //catch3-----开始
24: bipush 50
26: istore_1 //catch3------结束
27: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
30: iload_1
31: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
34: return
Exception table: //异常表中有三个异常,都是监测try中的代码
from to target type
3 6 9 Class java/lang/NegativeArraySizeException
3 6 16 Class java/lang/NullPointerException
3 6 23 Class java/nio/file/FileSystemAlreadyExistsException
LineNumberTable:
line 8: 0
line 10: 3
line 17: 6
line 11: 9
line 12: 10
line 17: 13
line 13: 16
line 14: 17
line 17: 20
line 15: 23
line 16: 24
line 18: 27
line 19: 34
LocalVariableTable:
Start Length Slot Name Signature
10 3 2 e Ljava/lang/NegativeArraySizeException;
17 3 2 e Ljava/lang/NullPointerException;
24 3 2 e Ljava/nio/file/FileSystemAlreadyExistsException;
0 35 0 args [Ljava/lang/String;
3 32 1 i I
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/NegativeArraySizeException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/NullPointerException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/nio/file/FileSystemAlreadyExistsException ]
frame_type = 3 /* same */
}
1.1.3 catch多个异常的简写
这是上一小节中的catch
多个异常的简写形式:
import java.nio.file.FileSystemAlreadyExistsException;
public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException | NullPointerException | FileSystemAlreadyExistsException e){
i = 30;
}
System.out.println(i);
}
}
对应的字节码信息和上一节中的相同:
{
public com.basic.FinallyDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_1
6: goto 13
9: astore_2
10: bipush 30
12: istore_1
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_1
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
20: return
Exception table:
from to target type
3 6 9 Class java/lang/NegativeArraySizeException
3 6 9 Class java/lang/NullPointerException
3 6 9 Class java/nio/file/FileSystemAlreadyExistsException
LineNumberTable:
line 8: 0
line 10: 3
line 13: 6
line 11: 9
line 12: 10
line 14: 13
line 15: 20
LocalVariableTable:
Start Length Slot Name Signature
10 3 2 e Ljava/lang/RuntimeException;
0 21 0 args [Ljava/lang/String;
3 18 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/RuntimeException ]
frame_type = 3 /* same */
}
1.2 finally
1.2.1 在finally中return *;
查看FinallyDemo.java
文件的代码,试问最终输出多少?10? or 20? or 30?
FinallyDemo.java
public class FinallyDemo {
public static void main(String[] args) {
FinallyDemo finallyDemo = new FinallyDemo();
int t = finallyDemo.test();
System.out.println(t);
}
public int test(){
int i = 10;
try {
return 20;
}finally {
return 30;
}
}
}
运行结果:
通过对其FinallyDemo.class
文件进行反编译,可以分析得到为什么最终结果会是30。为了简化代码,这里就直接将源代码对应的字节码信息粘贴过来。
FinallyDemo.class
{
public com.basic.FinallyDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class com/basic/FinallyDemo 在堆中创建对象
3: dup
4: invokespecial #3 // Method "<init>":()V 调用FinallyDemo构造方法
7: astore_1 // 将新创建的对象存储到局部变量slot 1中
8: aload_1 //加载局部变量slot 1中的对象
9: invokevirtual #4 // Method test:()I //执行slot 1中的test()方法
12: istore_2 //将test()返回的int类型存储到int类型的局部变量slot 2中
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 获得静态输出方法
16: iload_2 //将在需要打印的变量 slot 2
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 调用静态对象的println方法,并调用需要打印的变量
20: return
LineNumberTable:
line 5: 0
line 6: 8
line 7: 13
line 8: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 args [Ljava/lang/String;
8 13 1 finallyDemo Lcom/basic/FinallyDemo;
13 8 2 t I
public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=4, args_size=1
0: bipush 10 //将10压入栈顶
2: istore_1 // 10 -> i
3: bipush 20 //在try中------, 将20压入栈顶
5: istore_2 //20 -> slot 2 该局部变量是一个隐藏的局部变量,LocalVariableTable不显示
6: bipush 30 //将30压入栈顶
8: ireturn //----try结束 返回栈顶元素 30
9: astore_3 //在finally中-------
10: bipush 30 //将30压入栈顶
12: ireturn //finally------结束,返回栈顶元素
Exception table:
from to target type
3 6 9 any
LineNumberTable:
line 11: 0
line 13: 3
line 15: 6
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/basic/FinallyDemo;
3 10 1 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 9
locals = [ class com/basic/FinallyDemo, int ]
stack = [ class java/lang/Throwable ]
}
SourceFile: "FinallyDemo.java"
-
分析:
我们从反编译之后的字节码可以看出,之所以
finally
中的代码最后都会被执行,是因为在字节码中,将finally
中的代码复制了一份,所以才会总是执行。
1.2.2 在finally中使用return的弊端
使用try..catch
捕捉程序异常,但是有些时候,catch
不能将所有的异常都捕捉到,此时那些没有被catch
捕捉到的异常就会在finally
中被再次捕捉到,这样就提高了程序运行的安全性。但是当在finally
中使用return
语句后,那么finally
就会将其他的异常”吞掉“,也就是不再捕捉catch
中没有被捕捉的异常。
1.2.2.1 finally
中没有加return
语句:
public class FinallyDemo {
public static void main(String[] args) {
int i = 10;
try{
i = 20;
}catch (NegativeArraySizeException e){
e.printStackTrace();
}finally {
i = 30;
}
}
}
为了简便,这里主要粘贴源代码对应的字节码信息:
{
public com.basic.FinallyDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: bipush 10 //将10压入栈顶,全部变量
2: istore_1 //将10存储到slot 1中,10 -> i
3: bipush 20 //try-----开始, 将20压入栈顶
5: istore_1 //将20存储到slot 1中, 20 -> i
6: bipush 30 //将30压入栈顶,
8: istore_1 //将30存储到slot 1中,30 -> i try----结束
9: goto 29
12: astore_2 //catch------开始
13: aload_2
14: invokevirtual #3 // Method java/lang/NegativeArraySizeException.printStackTrace:()V
17: bipush 30
19: istore_1 //catch------结束
20: goto 29
23: astore_3 //finally----开始
24: bipush 30
26: istore_1
27: aload_3
28: athrow //finally------结束,抛出在finally里中捕捉到的异常
29: return
Exception table: //异常表,存放异常的跳转的起始位置,左闭右开区间,以第一个为例,从第3行到第5行
from to target type
3 6 12 Class java/lang/NegativeArraySizeException //监测try中的代码
3 6 23 any //finally在try中的跳转
12 17 23 any //finally在catch中的跳转
LineNumberTable:
line 5: 0
line 7: 3
line 11: 6
line 12: 9
line 8: 12
line 9: 13
line 11: 17
line 12: 20
line 11: 23
line 12: 27
line 13: 29
LocalVariableTable:
Start Length Slot Name Signature
13 4 2 e Ljava/lang/NegativeArraySizeException;
0 30 0 args [Ljava/lang/String;
3 27 1 i I
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/NegativeArraySizeException ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */
}
1.2.2.2 finally
中加了return
语句
public class FinallyDemo {
public static void main(String[] args) {
int j = test();
System.out.println(j);
}
public static int test(){
int i = 0;
try{
return 10;
}finally {
return 20;
}
}
}
源代码对应的字节码:
{
public com.basic.FinallyDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/FinallyDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokestatic #2 // Method test:()I
3: istore_1
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_1
8: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
11: return
LineNumberTable:
line 8: 0
line 9: 4
line 10: 11
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 args [Ljava/lang/String;
4 8 1 j I
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: iconst_0 //定义变量i
1: istore_0
2: bipush 10 //try-----开始
4: istore_1
5: bipush 20
7: ireturn //try-----结束
8: astore_2 //finally---开始
9: bipush 20 // finally中并没有捕捉异常,也没有抛出异常
11: ireturn //finally---结束
Exception table:
from to target type
2 5 8 any
LineNumberTable:
line 13: 0
line 15: 2
line 17: 5
LocalVariableTable:
Start Length Slot Name Signature
2 10 0 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ int ]
stack = [ class java/lang/Throwable ]
}
我们从字节码中可以看到,finally
中并没有捕捉异常,也没有抛出异常,所以此种写法对于程序而言非常危险。下面看一段错误代码,但是程序并没有抛出异常:
public class FinallyDemo {
public static void main(String[] args) {
int j = test();
System.out.println(j);
}
public static int test(){
int i = 1;
try{
i = i / 0; //此处是一个异常代码
return i;
}finally {
return i;
}
}
}
运行结果:
所以,在写代码时,一定要主要不要写出这样的代码。
写在最后
欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。