• 第18章 finally子句


    18.1微型子例程

    字节码中的finally子句在方法内部的表现很像“微型子例程”。Java虚拟机在每个try语句块和与其相关的catch子句的结尾处都会“调用”finally子句的子例程。finally子句结束后(这里的结束指的是finally子句中最后一条语句正常执行完毕,不包括抛出异常,或执行return、comimie、 break等情况),隶属于这个finally子句的微型子例程执行“返回”操作。程序在第一次调用微型子例程的地方继续执行后面的语句。

    jsr指令是使Java虚拟机跳转到微型子例程的操作码。jsr指令使用一个双字节长度的操作数, 这个操作数指出从jsr指令处到微型子例程开始处的16位带符号的偏移量。另外一条指令是jsr_w, 它与jsr完成同样的功能,但是它支持更长的操作数(4个字节长)。当Java虚拟机遇到jsr或者jsr_w 指令,它会把返回地址压人桟,然后从微型子例程的开始处继续执行。返回地址是紧接在jsr或jsr_w 操作码和操作数后字节码的地址(偏移量或者本地指针)。该地址的类型为returnAddress。

    微型子例程执行完毕后,将调用ret指令,ret指令的功能是执行从子例程中返回的操作。ret 指令只有一个操作数,这个操作数是一个存储返回地址的局部变量的索引。表18-1中总结了处理finally子句的操作码。

    不要混淆微型子例程与Java方法。Java方法与微型子例程使用不同的指令集。例如,调用 Java方法可以使用invokevirtual和invokespecial等指令,使Java方法返回可以使用return,areturn, ireturn等指令。

    jsr指令并不会调用Java方法,它只能跳转到相同方法中不同的操作码处。同样,ret指令也不能令Java方法返回,它只能使虚拟机跳回相同方法中调用jsr操作码和它的操作数之后的位置。 本书中,实现finally子句的字节码被称为“微型子例程”,因为它们在一个方法的字节码流中的 表现如同一个小子例程一样。

    18.2不对称的调用和返回

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

    例如,下面的代码包含了一个通过break语句退出的finally子句。执行这段代码的结果是, 无论给方法surpriseTheProgrammer ()的参数bVal传入什么,该方法都将返回false。
    // On CD-ROM in file opcodes/ex3/Surprise.java
    class Surprise {

    static boolean surpriseTheProgrammer(boolean bVal) {
    while (bVal) {
    try {
    return true;
    }
    finally {
    break;
    }
    }
    return false;
    }
    }

    上面的例子指出了返回地址必须在finally子句开始之处存入局部变量中的原因。因为finally子句在break语句处退出,它绝不会执行ret指令。因此,Java虚拟机不会执行返回true的语句。当 它执行完break语句后,会退出至while语句的终结处,即闭括号处。下一条语句将会返回false, 这个推断与Java虚拟机的执行过程完全相同。

    无论使用break、return、continue,还是通过抛出异常退出finally子句,所显示出的特性都是相同的。在这些例子中,finally子句结尾处的ret指令永远不会执行到。正因为它永远不会被执行到,就无法指望它从栈中除去返回地址。因此,虚拟机在finally子句开始的地方就将返回地址存储到局部变量中。

    下面所列出的方法是finally子句的一个完全范例,它包含了一个try语句块,这个try语句块中有两个退出点。在这个例子里,两个退出点都是return语句。
    // On CD-ROM in file opcodes/ex1/Nostalgia.java
    class Nostalgia {

    static int giveMeThatOldFashionedBoolean(boolean bVal) {
    try {
    if (bVal) {
    return 1;
    }
    return 0;
    }
    finally {
    System.out.println("Got old fashioned.");
    }
    }
    }
    方法giveMeThatOldFashionedBoolean()被编译为如下的字节码:
    //The bytecode sequence for the try block:
    0 iload_0 // Push local variable 0 (bval parameter)
    1 ifeq 11 // Pop int, if equal to 0, jump to 11 (just // past the if statement):if(bval){}
    4 iconst_1 // Push int 1
    // Pop an int (the 1), store into local
    5 istore_1 // variable 1
    // Jump to the Mini-subroutine for the
    6 jsr 24 // finally clause
    9 iload_1 // Push local variable 1 (the 1)
    // Return int on top of the stack (the 1):
    10 ireturn // return 1;
    11 iconst_0 // Push int 0
    // Pop an int (the 0), store into local
    12 istore_1 // variable 1
    // jump to the mini-subroutine for the
    13 jsr 24 // finally clause
    16 iload_l // Push local variable 1 (the 0)
    // Return int on top of the stack (the 0):

    17 ireturn // return 0;
    // the bytecode sequence for a catch clause that catches
    // any kind of exception thrown from within the try block.
    // Pop the reference to the thrown exception,
    18 astore_2 // store into local variable 2
    //Jump to the mini-subroutine for the
    19 jsr 24 // finally clause
    // Push the reference (to the thrown
    22 aload_2 // exception) from local variable 2
    23 athrow // Rethrow the same exception

    // The miniature subroutine that implements the finally
    // block.
    // Pop the return address, store it in local
    24 astore_3 // variable 3
    // Get a reference to java.lang.System.out

    25 getstatic #7 <Field java.io.PrintStream out>
    // Puah reference to "Got old fashioned."
    // String from the constant pool
    28 ldc #1 <String "Got old fashioned.">
    //Invoke System.out.printIn()

    30 invokevirtual #8
    <Method void printIn(java.lang.String)>
    // Return to return address stored in local
    33 ret 3 // variable 3

    try语句块的字节码中包含了两条jsr指令,此外,catch子句里还有一条jsr指令。如果在执行try语句块过程中抛出了一个异常,最后的语句块必须接着执行。因此,编译器在字节码中加入了catch子句。所以这里的catch子句只调用了描述finally子句的微型子例程,然后再抛出一个同 样的异常。如下所列的giveMeThatOldFashionedBoolean ()方法的异常表指出:凡是在地址0到17(含)之间(即所有实现try语句块的字节码中)抛出的异常,都被从地址18开始的catch子句处理。
    Exception table:
    from to target type
    0 18 18 any
    开始执行finally子句时,返回地址被弹出桟并被保存在局部变量3中。在finally子句结束的时候,ret指令再从局部变量3中取得返回地址。
    由javac产生的hopAround()方法的字节码如下所示:
    Exception table:
    from to target type
    2 4 10 any
    2 31 31 any
    hopAround ()方法在第一条finally子句中通过执行到闭括号返回,而它在第二条finally子句中是通过执行一条continue语句返回。第一条finally子句通过它的ret指令退出。而由于第二条finally子句使用continue退出,这样就不会执行它的ret指令。continue语句使Java虚拟机跳转至while循环的开始处。尽管方法中有return语句,但执行这条return语句前,将会首先执行第二条 finally子句,于是得出结论:这个循环是一个死循环。在finally子句中的continue语句取代了 return语句,这个方法永远无法返回。

    需要注意的是,实现return语句的字节码在跳转到实现第二条finally子句的微型子例程的时, 已经把返回值存入到局部变量1中。在微型子例程返回后(在上述情况下,它永远无法返回,因 为总是在返回之前执行continue语句),再从局部变量1中取出返回值,并且返回。

    这个过程强调了Java虚拟机在finally子句执行完毕前返回一个值的方式。尽管i的值是在执行完finally子句后返回的,但虚拟机仍将返回执行finally子句前i的值。所以就算finally子句会改变i的值,该方法仍将返回finally子句未执行前i的值。如果需要使用finally子句来改变方法返回值, 就不得不在finally子句中再加入一条return语句,用来返回被finally子句更新过的返回值。

  • 相关阅读:
    解决PLSQL Developer中文横着显示的问题
    品优购_day06
    品优购_day05
    品优购_day04
    品优购_day03
    品优购_day02
    java 学习中遇到的问题(二)泛型中<? extends T>和<? super T>的区别
    java 学习中遇到的问题(一)方法调用中的值传递和引用传递
    java中的字符串比较
    自学java 第十一章持有对象
  • 原文地址:https://www.cnblogs.com/mongotea/p/11980086.html
Copyright © 2020-2023  润新知