`
zcjl
  • 浏览: 42088 次
  • 性别: 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.

相关推荐

    java 中finally语句块与return的执行关系

    Java 中 finally 语句块与 return 的执行关系 Java 中的 finally 语句块是用于保证无论出现什么情况,一定要执行的代码块。在 try-catch-finally 结构中,finally 语句块的执行顺序是非常重要的。下面我们来详细...

    详解Java异常处理中finally子句的运用

    在异常处理中,`finally`子句扮演着关键角色,确保某些代码无论是否发生异常都会被执行。这对于清理资源,如关闭文件、网络连接或者释放内存等操作至关重要。 在Java中,异常处理通过`try-catch-finally`结构实现。...

    浅析Python中return和finally共同挖的坑

    #### finally子句的作用 `finally` 子句是 Python 异常处理机制的一部分,通常与 `try` 和 `except` 结合使用。无论 `try` 块中的代码是否引发异常,`finally` 块中的代码总会被执行。例如: ```python def test()...

    Python中的异常处理详解及try-except语句的工作原理.zip

    4. **finally子句**:`finally`块通常用于包含无论是否发生异常都需要执行的清理代码,如关闭文件、释放资源等。即使`try`或`except`块中包含`return`语句,`finally`块也会被执行。 ```python try: # 可能引发...

    简单介绍Python中的try和finally和with方法

    通过使用`try`和`finally`语句,我们可以有效地管理代码中可能出现的错误,并确保必要的清理工作得到执行。 ##### 1.1 try语句的基本用法 `try`语句允许我们指定一段可能会引发异常的代码块。当异常发生时,`try`...

    面向对象程序的设计2014复习试题.doc

    9. **finally子句的作用**:`finally`子句用于执行清理任务,如关闭文件或数据库连接。即使在`try`或`catch`块中有`return`语句或异常被抛出,`finally`块中的代码也会执行。 10. **异常处理策略**:当方法遇到它...

    Python中的异常处理try/except/finally/raise用法分析

    ### Python中的异常处理try/except/finally/raise用法分析 #### 一、异常处理概述 在编程过程中,经常会遇到一些不可预知的情况,这些情况会导致程序无法继续正常执行,这种情况被称为异常。为了确保程序的健壮性...

    python的异常处理.rar

    在Python中,异常是通过`try/except`块来捕获和处理的,而`else`和`finally`子句则提供了更高级别的控制和清理机制。下面将详细探讨这些概念。 首先,`try`块是用来包含可能抛出异常的代码的。当`try`块中的代码...

    Python异常处理(课件)

    除了`except`,`try...except`结构还可以包含`else`和`finally`子句。`else`块的代码只有在`try`块中没有发生异常时才会执行,而`finally`块的代码无论是否发生异常都会被执行,常用于资源清理和善后操作。 以下是...

    错误处理的艺术:精通JavaScript中的异常管理

    `finally`子句在`try...catch`语句中用于执行无论是否发生错误都需要执行的代码。这对于释放资源、清理操作等非常有用。下面是一个使用`finally`子句的例子: ```javascript try { // 尝试执行的代码 } catch ...

    2023年javasqloracle面试题汇总.doc

    6. **try-finally语句**:无论try块中的return语句是否执行,finally块中的代码都会在return之前执行,确保清理操作得以完成。 7. **冒泡排序**:冒泡排序是一种简单的排序算法,通过不断交换相邻的逆序元素逐步将...

    python基础教程:在python中利用try..except来代替if..else的用法

    同时,`finally` 子句可以用来确保在任何情况下(无论是否发生异常)都会执行的清理操作。 总的来说,`try...except` 是 Python 中处理错误和异常的重要工具,它可以提供更加健壮的代码,防止程序因未预见的错误而...

    2022国开形考任务-Java语言程序设计20.docx

    在Java语言中,try语句块用于捕获异常,catch语句块用于处理异常,而finally语句块用于释放资源。try语句块是异常发生的位置。catch语句块可以单独和finally语句块一起使用。 对于异常的说法正确的是,抛出异常是...

    XQuery基本使用语法

    选择和过滤元素是XQuery中的重要操作,这通常通过路径表达式或FLWOR表达式(For-Let-Where-Order-By-Return)来完成。以下示例展示了如何根据价格过滤书籍并按标题排序: ```xml for $x in doc("books.xml")/...

    Java Exception Handling 内容.ppt

    即使在`try`块中存在`return`语句,`finally`块也会执行。 ```java try { // ... } finally { // 清理代码 } ``` 15.7 栈展开(Stack Unwinding) 当异常发生时,执行流程会回溯到最近的匹配`catch`块,这个过程...

    Python基础练习.zip

    Python使用try/except语句块来捕获并处理可能出现的异常,通过finally子句确保某些代码无论是否发生异常都会执行。 此外,Python还支持面向对象编程,包括类的定义、对象的创建以及继承、封装和多态等概念。类是...

    11月java面试题汇总.pdf

    6. try-finally语句:finally块中的代码总会被执行,无论try块中是否有return语句。return语句会在finally块执行完毕后执行。 7. 冒泡排序实现:冒泡排序是一种简单的排序算法,通过重复遍历待排序的列表,比较相邻...

    简介JavaScript错误处理机制

    当在try或catch块中有return语句或其他退出执行的语句时,finally子句的代码依然会执行。finally子句的目的是提供一个统一的退出代码路径,无论是否发生异常,finally中的代码都会被执行,常用于进行资源释放等清理...

    Python异常处理操作实例详解

    即使在`try`和`except`块中有`return`语句,`finally`块的代码也会被执行。 7. **自定义异常** Python允许创建自定义异常类,通过继承`Exception`或其子类来实现。自定义异常有助于细化错误处理,提高代码可读性...

Global site tag (gtag.js) - Google Analytics