Java中finally关键字的几个坑

java中的 finally 关键字通常与 try/catch 块一起使用。用来在方法结束前或发生异常时做一些资源释放的操作。虽然看起来很简单,在日常开发中也发现几个关于 finlla问题。

finally 语句块一定会执行吗?

很多人都认为 finally 语句块是肯定要执行的,比如下面的代码,只要进入了 try/catch 块,不管有没有异常,都会执行 finllay 块 :

1
2
3
4
5
6
7
8
9
public static int test() {
try {
System.out.println("try block");
int i = 1 / 0;
return 0;
} finally {
System.out.println("finally block");
}
}

运行代码输出 :

1
2
3
try block
finally block
Exception in thread "main" java.lang.ArithmeticException: / by zero

但是回到这个问题,结果并不像大多人所认为的,答案是否定的,我们先来看下面这个例子:

1
2
3
4
5
6
7
8
9
public static int test() {
try {
System.out.println("try block");
System.exit(0);
return 0;
} finally {
System.out.println("finally block");
}
}

运行代码输出 :

1
try block

我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行,finally 语句块还是没有执行。

如果执行了finally,函数返回值问题

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int test() {
try {
System.out.println("try block");
int i = 1 / 0; // 注释
return 0;
} catch (Exception e) {
System.out.println("catch block");
return 1;
} finally {
System.out.println("finally block");
return 2;
}
}

对于上面的代码,相信大部分人都能知道输出值是2,打印结果也确实是2,就算把int i = 1 / 0这一行注释掉,打印结果也是2。所以在这里我们可以下结论 :finally里的return语句会把 try/catch 块里的return语句效果给覆盖掉。

假如我们不在finallyreturn,结果会怎样?我们再看看下面的例子 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static int test() {
int i = 999;
try {
System.out.println("try block");
i = 1 / 0;
return i;
} catch (Exception e) {
System.out.println("catch block");
i = 100;
return i;
} finally {
System.out.println("finally block");
i = 200;
}
}

打印结果是 :

1
2
3
4
try block
catch block
finally block
100

虽然调用了finllay改变了i的值,但是最后输出还是100,为什么呢?我们可以通过分析字节码文件得到结果 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static int test();
Code:
0: sipush 999
3: istore_0
。。。
29: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
32: ldc #14 // String catch block
34: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: bipush 100
39: istore_0
40: iload_0
41: istore_2
42: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
45: ldc #12 // String finally block
47: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
50: sipush 200
53: istore_0
54: iload_2
55: ireturn
。。。

从字节码文件中可以看出,在37:39中把100存入index 0的位置,就是设置i的值为100,在40:41中把index 0的值赋值给了index 2的位置,在50:53中把200存入index 0的位置,就是设置i的值为200,最后在54:55中把index 2的值加载出来并返回,即最后返回的是index 2的值100

对于这种情况我的理解就是在 return 的的时候会把返回值压入栈,并把返回值赋值给栈中的局部变量, 最后把栈顶的变量值作为函数返回值。所以在finally中的返回值就会覆盖try/catch中的返回值,如果finally中不执行return语句,在finally中修改返回变量的值,不会影响返回结果。

总结

  • 如果在 try/catch 中调用 System.exit (0) 语句,终止了 Java 虚拟机的运行,是不会调用 finally 代码块的;
  • finally里的return语句会把 try/catch 块里的 return 语句效果给覆盖掉。
  • return 会把返回值赋值给返回栈中的局部变量,所以在return之后(比如在finally中)修改返回变量的值是无效的;