不管 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. [C语言]小知识点 持续更新

    2019-11-24 1.如果输入: printf(,)); 会得到0: 这和我们的日常判断不相符! 然而,改成: printf(,)); 就可以成功输出“2”: 因此,注意pow函数返回的是浮点数, ...

  2. 乐字节Java之file、IO流基础知识和操作步骤

    嗨喽,小乐又来了,今天要给大家送上的技术文章是Java重点知识-IO流. 先来看看IO流的思维导图吧. 一. File 在Java中,Everything is Object!所以在文件中,也不例外! ...

  3. [CF1051F]The Shortest Statement_堆优化dij_最短路树_倍增lca

    The Shortest Statement 题目链接:https://codeforces.com/contest/1051/problem/F 数据范围:略. 题解: 关于这个题,有一个重要的性质 ...

  4. PHP与Memcached服务器交互的分布式实现源码分析

    转自: http://blog.csdn.net/hguisu/article/details/7353595 前段时间,因为一个项目的关系,研究了php通过调用memcache和memcached ...

  5. LeetCode 189. 旋转数组(Rotate Array)

    189. 旋转数组 LeetCode189. Rotate Array 题目描述 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 示例 1: 输入: [1,2,3,4,5,6, ...

  6. [Xamarin] - 连接 Mac Agent 显示 "couldn't connect to xxxx, please try again" 之解决

    背景 在 VS 2017 的 Xamarin 项目中,配置 Mac Agent 连接到本地虚拟机中的 MacOS 失败. 1. MacOS 已启用远程登陆.2. SSH 可以登陆成功.3. 防火墙已关 ...

  7. html页面在苹果手机内,safari浏览器,微信中滑动不流畅问题解决方案

    1. -webkit-overflow-scrolling:touch是什么? MDN上是这样定义的: -webkit-overflow-scrolling 属性控制元素在移动设备上是否使用滚动回弹效 ...

  8. 正整数序列 Help the needed for Dexter ,UVa 11384

    题目描述 Description 给定正整数n,你的任务是用最少的操作次数把序列1, 2, …, n中的所有数都变成0.每次操作可从序列中选择一个或多个整数,同时减去一个相同的正整数.比如,1,2,3 ...

  9. 图片上传怎么用File接受文件

    xl_echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.——这才是真正的堪称强大!! - ...

  10. 数据结构与算法(周测3-Huffman树)

    判断题 1.Given a Huffman tree for N (≥2) characters, all with different weights. The weight of any non- ...