不管 try 语句块正常结束还是异常结束,finally 语句块是保证要执行的。如果 try 语句块正常结束,那么在 try 语句块中的语句都执行完之后,再执行 finally 语句块。如果 try 中有控制转移语句(return、break、continue)呢?那 finally 语句块是在控制转移语句之前执行,还是之后执行呢?

finally 语句示例说明

下面,我们先来看一个简单的例子(清单 1)。

清单 1.
1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) { 
try { 
System.out.println("try block"); 
 
return ; 
} finally { 
System.out.println("finally block"); 
        
    
}

清单 1 的执行结果为:

1
2
try block
finally block

清单 1 说明 finally 语句块在 try 语句块中的 return 语句之前执行。我们再来看另一个例子(清单 2)。

清单 2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) { 
System.out.println("reture value of test() : " + test());
    }
    
public static int test(){
int i = 1;
        
try { 
System.out.println("try block"); 
            i = 1 / 0;
return 1; 
}catch (Exception e){
System.out.println("exception block");
return 2;
}finally { 
System.out.println("finally block"); 
        }
    }
}

清单 2 的执行结果为:

1
2
3
4
try block
exception block
finally block
reture value of test() : 2

清单 2 说明了 finally 语句块在 catch 语句块中的 return 语句之前执行。

从上面的清单 1 和清单 2,我们可以看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。

清单 3.
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
       System.out.println("return value of getValue(): " + getValue());
    }
 
public static int getValue() {
       try {
                return 0;
       } finally {
                return 1;
           }
    }
}

清单 3 的执行结果:

1
return value of getValue(): 1
清单 4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
       System.out.println("return value of getValue(): " + getValue());
    }
 
public static int getValue() {
       int i = 1;
       try {
                return i;
       } finally {
                i++;
       }
    }
}

清单 4 的执行结果:

1
return value of getValue(): 1

利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。 由此,可以轻松的理解清单 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。那为什么清单 4 的返回值不是 2,而是 1 呢?按照清单 3 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?

关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

是不是不太好理解,那我们就用具体的例子来做形象的说明吧!

为了能够解释清单 4 的执行结果,我们来分析一下清单 4的字节码(byte-code):

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
37
38
39
40
41
42
43
44
45
46
47
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
 Code:
  0:    aload_0
  1:invokespecial#1; //Method java/lang/Object."<init>":()V
  4:    return
 
 LineNumberTable:
  line 1: 0
 
public static void main(java.lang.String[]);
 Code:
  0:    getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
  3:    new     #3; //class java/lang/StringBuilder
  6:    dup
  7:    invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V
  10:   ldc     #5; //String return value of getValue():
  12:   invokevirtual  
  #6; //Method java/lang/StringBuilder.append:(
      Ljava/lang/String;)Ljava/lang/StringBuilder;
  15:   invokestatic    #7; //Method getValue:()I
  18:   invokevirtual  
  #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  21:   invokevirtual  
  #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  24:   invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
  27:   return
 
public static int getValue();
 Code:
  0:    iconst_1
  1:    istore_0
  2:    iload_0
  3:    istore_1
  4:    iinc    0, 1
  7:    iload_1
  8:    ireturn
  9:    astore_2
  10:   iinc    0, 1
  13:   aload_2
  14:   athrow
 Exception table:
  from   to  target type
    2     4     9   any
    9    10     9   any
}

对于 Test()构造方法与 main()方法,在这里,我们不做过多解释。让我们来分析一下 getValue()方法的执行。在这之前,先让我把 getValue()中用到的虚拟机指令解释一下,以便读者能够正确的理解该函数的执行。

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
37
38
39
40
41
42
43
44
45
46
1.  iconst_
Description: Push the int constant  (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.
Forms: iconst_m1 = 2 (0x2)  iconst_0 = 3 (0x3)  iconst_1 = 4 (0x4) 
iconst_2 = 5 (0x5) iconst_3 = 6 (0x6)  iconst_4 = 7 (0x7)  iconst_5 = 8 (0x8)
 
2.  istore_
Description: Store int into local variable. The  must be an index into the
local variable array of the current frame.
Forms: istore_0 = 59 (0x3b)  istore_1 = 60 (0x3c)  istore_2 = 61 (0x3d) 
istore_3 = 62 (0x3e)
 
3.  iload_
Description: Load int from local variable. The  must be an index into the
local variable array of the current frame.
Forms: iload_0 = 26 (0x1a)  iload_1 = 27 (0x1b)  iload_2 = 28 (0x1c)  iload_3 = 29 (0x1d)
 
4.  iinc index, const
Description: Increment local variable by constant. The index is an unsigned byte that
must be an index into the local variable array of the current frame. The const is an
immediate signed byte. The local variable at index must contain an int. The value
const is first sign-extended to an int, and then the local variable at index is
incremented by that amount.
Forms:  iinc = 132 (0x84)
 
Format:
iinc   
index  
const  
 
5.  ireturn
Description: Return int from method.
Forms:  ireturn = 172 (0xac)
 
6.  astore_
Description: Store reference into local variable. The  must be an index into the
local variable array of the current frame.
Forms: astore_0 = 75 (0x4b) astore_1 = 76 (0x4c) astore_2 =77 (0x4d) astore_3 =78 (0x4e)
 
7.  aload_
Description: Load reference from local variable. The  must be an index into the
local variable array of the current frame.
Forms: aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d)
 
8.  athrow
Description: Throw exception or error.
Forms: athrow = 191 (0xbf)

有了以上的 Java 虚拟机指令,我们来分析一下其执行顺序:分为正常执行(没有 exception)和异常执行(有 exception)两种情况。我们先来看一下正常执行的情况,如图 1 所示:

图 1. getValue()函数正常执行的情况

由上图,我们可以清晰的看出,在 finally 语句块(iinc 0, 1)执行之前,getValue()方法保存了其返回值(1)到本地表量表中 1 的位置,完成这个任务的指令是 istore_1;然后执行 finally 语句块(iinc 0, 1),finally 语句块把位于 0 这个位置的本地变量表中的值加 1,变成 2;待 finally 语句块执行完毕之后,把本地表量表中 1 的位置上值恢复到操作数栈(iload_1),最后执行 ireturn 指令把当前操作数栈中的值(1)返回给其调用者(main)。这就是为什么清单 6 的执行结果是 1,而不是 2 的原因。

再让我们来看看异常执行的情况。是不是有人会问,你的清单 6 中都没有 catch 语句,哪来的异常处理呢?我觉得这是一个好问题,其实,即使没有 catch 语句,Java 编译器编译出的字节码中还是有默认的异常处理的,别忘了,除了需要捕获的异常,还可能有不需捕获的异常(如:RunTimeException 和 Error)。

从 getValue()方法的字节码中,我们可以看到它的异常处理表(exception table), 如下:

Exception table:

from to target type

2 4 9 any

它的意思是说:如果从 2 到 4 这段指令出现异常,则由从 9 开始的指令来处理。

图 2. getValue()函数异常执行的情况

先说明一点,上图中的 exception 其实应该是 exception 对象的引用,为了方便说明,我直接把它写成 exception 了。

由上图(图 2)可知,当从 2 到 4 这段指令出现异常时,将会产生一个 exception 对象,并且把它压入当前操作数栈的栈顶。接下来是 astore_2 这条指令,它负责把 exception 对象保存到本地变量表中 2 的位置,然后执行 finally 语句块,待 finally 语句块执行完毕后,再由 aload_2 这条指令把预先存储的 exception 对象恢复到操作数栈中,最后由 athrow 指令将其返回给该方法的调用者(main)。

清单 5.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
       System.out.println("return value of getValue(): " + getValue());
    }
 
@SuppressWarnings("finally")
public static int getValue() {
       int i = 1;
       try {
                i = 4;
       } finally {
                i++;
                return i;
       }
    }
}

清单 5 的执行结果:

1
return value of getValue(): 5
清单 6.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
       System.out.println("return value of getValue(): " + getValue());
    }
 
public static int getValue() {
       int i = 1;
       try {
                i = 4;
       } finally {
                i++;
       }
        
       return i;
    }
}

清单 6 的执行结果:

1
return value of getValue(): 5

清单 5 和清单 6 应该还比较简单吧!利用我们上面讲解的知识,很容易分析出其结果。让我们再来看一个稍微复杂一点的例子 – 清单 7。我建议大家最好先不要看执行结果,运用学过的知识来分析一下,看是否能推断出正确的结果。

清单 7.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) { 
System.out.println(test()); 
    
 
public static String test() { 
try { 
System.out.println("try block"); 
return test1(); 
} finally { 
System.out.println("finally block"); 
        
    
public static String test1() { 
System.out.println("return statement"); 
return "after return"; 
    
}

清单 7 的结果:

1
2
3
4
try block
return statement
finally block
after return

你分析对了吗?其实这个案例也不算很难,return test1();这条语句等同于 :

1
2
String tmp = test1();
return tmp;

try中如果return的是一个引用而不是基本数据类型, 那么在finally中改变这个引用,就会影响return的返回值

public class TryCatchFinally {

    class Test{
int a = 20;
int b = 10;
public void setA(int aa){
this.a=aa;
}
public void setB(int bb){
this.b=bb;
}
public int getA(){
return this.a;
}
public int getB(){
return this.b;
}
} public static void main(String[] args){
TryCatchFinally tryCatchFinally = new TryCatchFinally();
Test t = tryCatchFinally.testFinally();
System.out.println("引用类型的finally测试: " + t.getA() + " ; " + t.getB()); System.out.println("========================");
int sum = tryCatchFinally.testFinally1();
System.out.println("基本数据类型的finally测试: " + sum);
}
public Test testFinally(){
Test test = new Test(); try{
System.out.println("try");
return test;
}catch (Exception e){
System.out.println("catch");
}finally {
System.out.println("finally");
test.setA(100);
test.setB(200);
}
return test;
}
public int testFinally1(){
int s1 = 2;
int s2 = 3;
try{
System.out.println("try");
return s1+s2;
}catch (Exception e){
System.out.println("catch");
}finally {
System.out.println("finally");
s1 = 100;
s2 = 100;
}
return s1+s2;
}
}

输出结果:

try
finally
引用类型的finally测试: 100 ; 200
========================
try
finally
基本数据类型的finally测试: 5

finally代码块不一定被执行, 以下两种情况finally代码块不被执行:

1).当程序进入try语句块之前就出现异常, 此时会直接结束

例如:

int i = 5/0;//此处出现异常,根本就不会进入try语句块

try{

   System.out.println("hello");

}catch(Exception e){

  System.out.println("aaa");

}finally{

  System.out.println("finally");

}

2).当程序在try块中强制退出时也不会执行finally块中的代码.

例如:

try{

  System.out.println("try block");

  System.exit(0);//强制退出系统,导致finally代码块不被执行

}catch(Exception e){

  System.out.println("catch block");

}finally{

  System.out.println("finally block");

}

java中的finally用法总结的更多相关文章

  1. java中this的用法?

    2008-07-28 08:10cztx5479 | 分类:JAVA相关 | 浏览4533次 java中this的用法? import java.awt.*; import java.awt.even ...

  2. 转:十八、java中this的用法

    http://blog.csdn.net/liujun13579/article/details/7732443 我知道很多朋友都和我一样:在JAVA程序中似乎经常见到“this”,自己也偶尔用到它, ...

  3. Java 中 synchronized的用法详解(四种用法)

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.本文给大家介绍java中 synchronized的用法,对本文感兴趣的朋友一起看看吧 ...

  4. java成神之——java中string的用法

    java中String的用法 String基本用法 String分割 String拼接 String截取 String换行符和format格式化 String反转字符串和去除空白字符 String获取 ...

  5. java中stringBuilder的用法

    java中stringBuilder的用法 String对象是不可改变的.每次使用 System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需 ...

  6. 第一篇 网站基础知识 第4章 Java中Socket的用法

    第4章 Java中Socket的用法 4.1 普通Socket的用法 Java中的网络通信是通过Socket实现的,Socket分为ServetSocket和Socket两大类,ServetSocke ...

  7. Java中的Socket用法

    转发链接:https://www.cnblogs.com/zhanglei93/p/6217384.html (1)Java中的Socket用法 Java中的Socket分为普通的Socket和Nio ...

  8. 初探java中this的用法

    一般this在各类语言中都表示“调用当前函数的对象”,java中也存在这种用法: public class Leaf { int i = 0; Leaf increment(){ i++; retur ...

  9. Java中getResourceAsStream的用法

    首先,Java中的getResourceAsStream有以下几种: 1. Class.getResourceAsStream(String path) : path 不以'/'开头时默认是从此类所在 ...

  10. Java中Synchronized的用法

    原文:http://blog.csdn.net/luoweifu/article/details/46613015 作者:luoweifu 转载请标名出处 <编程思想之多线程与多进程(1)——以 ...

随机推荐

  1. Appium移动自动化测试-----(六)1.appium-desktop下载安装

    Appium 移动测试中有个很重新的组件 Appium-Server,它主要用来监听我们的移动设备(真机或模拟器),然将不同编程语言编写的 appium 测试脚本进行解析,然后,驱动移动设备来运行测试 ...

  2. [HTTPS] - 请求API失败(Could not create SSL/TLS secure channel)之解决

    背景 在单元测试中请求 HTTPS API 失败. 异常 Result StackTrace:  at System.Web.Services.Protocols.WebClientProtocol. ...

  3. 批量删除c文件和h文件中的注释

    不知道大家有没有批量删除c文件和h文件中注释的需要,说起来搞笑,偶然翻出来早先写的一份,首先楼猪不是闲的蛋疼写这东西,工作需要,哪里要砖就要搬.冷门的东西大家需要的时候也不一定好找,分享给大家,省的自 ...

  4. 第四章 函数之lambda 表达式和内置函数

    4.5 lambda 表达式 用于表示简单的函数. # 三元运算,为了解决简单的if else的情况,如:if 1 == 1:    a = 123else:    a = 456# 相当于a = 1 ...

  5. Mysql之rpm安装5.7版本遇见的问题

    前言:环境是centos7.5的系统,用rpm方式安装mysql5.7 1.由于是centos7.5 所以需要将默认的mariadb给卸载 rpm -qa | grep mariadb 查看下是否有m ...

  6. golang之结构体结构体嵌入和匿名成员

    考虑一个二维的绘图程序,提供了一个各种图形的库,例如矩形.椭圆形.星形和轮形等几何形状.这里是其中两个的定义: type Circle struct { X, Y, Radius int } type ...

  7. WUSTOJ 1326: Graph(Java)费马数

    题目链接:1326: Graph 参考博客:HNUSTOJ-1617 Graph(费马数)--G2MI Description Your task is to judge whether a regu ...

  8. Authentication源码解析

    1.获取当前的 Subject. 调用 SecurityUtils.getSubject(); 从当前线程的threadLocals属性中获取Subject对象 SecurityUtils publi ...

  9. Angular 学习笔记 (Angular 9 & ivy)

    refer : https://blog.angularindepth.com/all-you-need-to-know-about-ivy-the-new-angular-engine-9cde47 ...

  10. 启动 docker 容器时报错

    错误信息: iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9300 -j DNAT --to-dest ...