`
zcjl
  • 浏览: 41675 次
  • 性别: Icon_minigender_1
  • 来自: 70码之城
社区版块
存档分类
最新评论

finally子句和try子句中return的先后关系

阅读更多

finally子句和try子句中return的先后关系

这个问题起因于java区基础版块的一篇帖子:
<a href="http://community.csdn.net/Expert/topic/3636/3636856.xml?temp=.9524347" target="_blank">http://community.csdn.net/Expert/topic/3636/3636856.xml?temp=.9524347</a>


下面是我从《深入Java虚拟机 2E》(中文版,曹晓钢 蒋靖译)中得到的解释
由于现学现卖,不免有大量摘抄的地方,望诸位谅解。

jsr指令是使java虚拟机跳转到微型子例程[注释1]的操作码,另外一条指令使jsr_w,后者支持比前者更长的操作数(4个字节长)。当java虚拟机遇到jsr或是jsr_w指令,它会把返回地址压入栈,然后从微型子例程的开始处继续执行。

微型子例程执行完毕后(这里指的是finally子句中最后一条语句正常执行完毕,不包括抛出异常,或执行return、continue、break等情况),将调用ret指令,ret指令的功能是执行从子例程中返回的操作。

你也许会认为,ret指令应当从栈中弹出返回地址,因为返回地址也已被jsr指令压入栈。不是这样的,ret指令并不会这样做。在每一个子例程的开始处,返回地址都从栈顶端弹出,并且存储在局部变量中,稍后,ret指令将会从这个局部变量中取出返回地址。这种对返回地址的不对称的工作方式是必要的,因为finally子句本身会抛出异常或者含有return、break、continue等语句。由于这些可能性的存在,这个被jsr指令压入栈的额外返回地址必须立即从栈中移除。因此,当finally子句通过break、continue、return或者抛出异常退出时,这个问题就不必再考虑了。

先看主帖里的示例代码(为了bytecode的清晰,去掉了无关的打印语句和捕获异常语句)
// Test1.java
public class Test1{
    public static void main(String[] args){
        System.out.print(tt());
    }

    public static int tt(){
        int b = 23;
        try{
            return b = 88;
        }
        finally {
            if(b > 25){
                System.out.println("b > 25 : "+b);
            }
        }
    }
}

//调用javap -c Test1后得到的字节码序列
Compiled from "Test1.java"
public class Test1 extends java.lang.Object{
public Test1();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   invokestatic    #3; //Method tt:()I
   6:   invokevirtual   #4; //Method java/io/PrintStream.print:(I)V
   9:   return

public static int tt();
  Code:
   0:   bipush  23      // 将数据23转换为int类型,然后将其压入栈
   2:   istore_0        // 从栈中弹出int类型值,然后将其存到位置为0的局部变量中
                        // 执行int b = 23;

   3:   bipush  88      // 将数据88转换为int类型,然后将其压入栈
   5:   dup             // 复制栈顶部的一个字,然后再将复制内容压入栈
                        // 这里是执行b = 88语句

   6:   istore_0        // 从栈中弹出int类型值,然后将其存到位置为0的局部变量中
                        // 这里存的是88,b = 88语句的执行后b的值

   7:   istore_1        // 从栈中弹出int类型值,然后将其存到位置为1的局部变量中
                        // 这里存的是88,b = 88语句的执行结果,将要被return语句返回的值

   8:   jsr     19      // 把返回地址压入栈,跳转至偏移量指定位置处执行分支操作
                        // 这里先将指令8的偏移地址压入栈,然后跳转到指令19,finally子句的开始

   11:  iload_1         // 将位置为1的int类型局部变量压入栈
                        // 将先前存到位置为1的局部变量中的返回值压入栈

   12:  ireturn         // 从方法中返回int类型的数据
                        // 即try子句中最后的一步操作:return方法tt的的返回值

   13:  astore_2
   14:  jsr     19
   17:  aload_2
   18:  athrow
                        // 指令13-18是针对try子句产生异常的情况,这里不做分析

   19:  astore_3        // 从栈中弹出对象引用,然后将其存到位置为3的局部变量中
                        // finally子句的开始
                        // 这里弹出的是指令8(不发生异常的情况下)中压入栈的返回地址,原因在前面摘抄的文字中已经解释过了


   20:  iload_0         // 将位置为0的int类型局部变量压入栈
   21:  bipush  25      // 将数据25转换为int类型,然后将其压入栈
   23:  if_icmple       51
   26:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   29:  new             #5; //class StringBuffer
   32:  dup
   33:  invokespecial   #6; //Method java/lang/StringBuffer."<init>":()V
   36:  ldc             #7; //String b > 25 :
   38:  invokevirtual   #8; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   41:  iload_0
   42:  invokevirtual   #9; //Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
   45:  invokevirtual   #10; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
   48:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
                        // 指令20-48执行if子句,略过,不做分析

   51:  ret     3       // 从子例程中返回到保存在局部变量3中的地址
                        // 呼应指令19,从finally子句转回到try子句
  Exception table:
   from   to  target type
     3    11    13   any
    13    17    13   any

}
------------------------------------------------------------------
上面的中文注释,直接对应在指令后面的是《深入java虚拟机》中的指令说明,其下的才是我所理解的行为。
可以看出,源代码中简单的一句return b = 88;语句,编译成bytecode后,对应了3-12条指令。其中指令8是一个分界点,指令3-7执行b = 88这个语句,且赋值运算后将b的值和方法的返回值分别存入到位置为0和1的局部变量中,指令11、12则从位置为1的局部变量中取出方法的返回值,并返回。指令8则是将自己的偏移地址压入栈,然后跳转到指令19,开始执行finally子句。
finally子句先将指令8的偏移地址弹出栈,并保存到位置为3的局部变量中,然后开始执行后面的语句。当后面的语句执行完毕,通过指令51,从位置为3的局部变量中取出指令8的偏移地址,然后返回执行指令8的后续指令(try子句中最终的return指令)。
(待续,明天再补上对finally子句中直接用写return 123;的理解)

注释:
1.字节码中的finally子句在方法内部的表现很像“微型子例程”,因此本文中的“微型子例程”特指finally子句。



分享到:
评论
1 楼 sdh5724 2009-02-28  
感觉finally更象closure.

相关推荐

    JSTL详细标签库介绍

    语句位置、try{}程序块、try()程序块中直接和间接调用的方法中)&lt;BR&gt;&lt;BR&gt;4、 java采用终止方式异常处理,不是恢复方式的异常处理&lt;BR&gt;&lt;BR&gt;5、 发生异常时,异常周围信息(抛出对象本身类型)-------------异常处理...

    Python上下文管理器和with块详解

    finally 子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。 ==上下文管理器协议包含enter和exit两个方法==。 with 语句开始运行时,会在上下文管理器对象上调用enter方法。 with 语句运行结束后,会在...

    JAVA基础课程讲义

    try, catch,finally ,return 执行顺序 100 异常的处理办法之二,声明异常: throws子句 101 方法重写中声明异常原则 102 异常的处理办法之三,手动抛出异常,throw子句 103 自定义异常 103 使用异常机制建议 104 ...

    java 面试题 总结

    如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的...

    超级有影响力霸气的Java面试题大全文档

    如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的...

    net学习笔记及其他代码应用

    43.try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后? 答:会执行,在return前执行。 44.两个对象值相同(x.equals(y) == true),但却可有不同...

    JAVA面试题最全集

    子句就会执行,然后控制就会进入 finally 块(如果有的话)。 finalize?方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这...

    微软C#语言规范,C#语言教程中文版

    7.10 关系和类型测试运算符 192 7.10.1 整数比较运算符 192 7.10.2 浮点比较运算符 193 7.10.3 小数比较运算符 194 7.10.4 布尔相等运算符 194 7.10.5 枚举比较运算符 194 7.10.6 引用类型相等运算符 194 7.10.7 ...

    C#语言规范(4.0版本)

    7.10 关系和类型测试运算符 192 7.10.1 整数比较运算符 192 7.10.2 浮点比较运算符 193 7.10.3 小数比较运算符 194 7.10.4 布尔相等运算符 194 7.10.5 枚举比较运算符 194 7.10.6 引用类型相等运算符 194 7.10.7 ...

    C#语言规范4.0

    7.10 关系和类型测试运算符 192 7.10.1 整数比较运算符 192 7.10.2 浮点比较运算符 193 7.10.3 小数比较运算符 194 7.10.4 布尔相等运算符 194 7.10.5 枚举比较运算符 194 7.10.6 引用类型相等运算符 194 7.10.7 ...

    C#语言规范(2.0,3.0,4.0合集)

    7.10 关系和类型测试运算符 192 7.10.1 整数比较运算符 192 7.10.2 浮点比较运算符 193 7.10.3 小数比较运算符 194 7.10.4 布尔相等运算符 194 7.10.5 枚举比较运算符 194 7.10.6 引用类型相等运算符 194 7.10.7 ...

    C#_语言规范_4.0_中文版

    7.10 关系和类型测试运算符 192 7.10.1 整数比较运算符 192 7.10.2 浮点比较运算符 193 7.10.3 小数比较运算符 194 7.10.4 布尔相等运算符 194 7.10.5 枚举比较运算符 194 7.10.6 引用类型相等运算符 194 7.10.7 ...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    它显示了根和它的导出类之间的关系。 子类从它先辈类那里继承了代码和数据,这样,它就可以执行先辈类的功能和访问先辈 类的数据。一个纯面向对象程序设计的语言将具有严格的继承性。 通过对象、类,我们实现了封装...

    C#教程(语言规范)

    7.10 关系和类型测试运算符 ... 171 7.10.1 整数比较运算符... 172 7.10.2 浮点比较运算符... 173 7.10.3 小数比较运算符... 173 7.10.4 布尔相等运算符... 173 7.10.5 枚举比较运算符... 174 7.10.6 引用类型...

    c#学习笔记.txt

    try-catch 语句由一个 try 块和其后所跟的一个或多个 catch 子句(为不同的异常指定处理程序)构成。try-catch 语句采用下列形式之一: try try-block catch (exception-declaration-1) catch-block-1 catch ...

    c#3.0语言规范高清PDF

    1.3 类型和变量 ................................................................................................................................................. 3 1.4 表达式 .............................

Global site tag (gtag.js) - Google Analytics