`
helloyesyes
  • 浏览: 1275252 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

finally知多少

阅读更多

先来看一下以下的代码,猜猜他们会是什么样的结果:

  1  public   class  FinallyIssue {
  2       public   static   void  main(String[] args) {
  3         System.out.println( " finallyReturnTest :  " );
  4         System.out.println( " return value :  "   +  finallyReturnTest( 1 ));
  5         System.out.println( " return value :  "   +  finallyReturnTest( - 1 ));
  6        
  7         System.out.println( " finallyBreakTest :  " );
  8         System.out.println( " return value :  "   +  finallyBreakTest( true ));
  9         System.out.println( " return value :  "   +  finallyBreakTest( false ));
 10        
 11         System.out.println( " valueChangeInFinallyTest :  " );
 12         System.out.println( " return value :  "   +  valueChangeInFinallyTest());
 13        
 14         System.out.println( " valueChangeReturnInFinallyTest :  " );
 15         System.out.println( " return value :  "   +  valueChangeReturnInFinallyTest());
 16        
 17         System.out.println( " refValueChangeInFinallyTest :  " );
 18         System.out.println( " return name :  "   +  refValueChangeInFinallyTest().name);
 19      }
 20     
 21       private   static   boolean  finallyReturnTest( int  value) {
 22          try  {
 23              if (value  >   0 ) {
 24                 return   true ;
 25             }  else  {
 26                 return   false ;
 27             }
 28         }  finally  {
 29              return   false ;
 30         }
 31      }
 32     
 33       private   static   boolean  finallyBreakTest( boolean  value) {
 34          while (value) {
 35              try  {
 36                 return   true ;
 37             }  finally  {
 38                 break ;
 39             }
 40         }
 41          return   false ;
 42      }
 43     
 44       private   static   int  valueChangeInFinallyTest() {
 45          int  i  =   10 ;
 46          int  j  =   1 ;
 47          try  {
 48             i  =   100 ;
 49             j  =   2 ;
 50             System.out.println( " try : i =  "   +  i);
 51             System.out.println( " try : j =  "   +  j);
 52              return  i;
 53         }  catch (Exception e) {
 54             e.printStackTrace();
 55         }  finally  {
 56             i  =   1000 ;
 57             j  =   3 ;
 58             System.out.println( " finally : i =  "   +  i);
 59             System.out.println( " finally : j =  "   +  j);
 60         }
 61        
 62          return  i;
 63      }
 64     
 65       private   static   int  valueChangeReturnInFinallyTest() {
 66          int  i  =   10 ;
 67          int  j  =   1 ;
 68          try  {
 69             i  =   100 ;
 70             j  =   2 ;
 71             System.out.println( " try : i =  "   +  i);
 72             System.out.println( " try : j =  "   +  j);
 73              return  i;
 74         }  catch (Exception e) {
 75             e.printStackTrace();
 76         }  finally  {
 77             i  =   1000 ;
 78             j  =   3 ;
 79             System.out.println( " finally : i =  "   +  i);
 80             System.out.println( " finally : j =  "   +  j);
 81              return  i;
 82         }
 83      }
 84     
 85       private   static  Person refValueChangeInFinallyTest() {
 86         Person p  =   new  Person();
 87          try  {
 88             p.name  =   " person1 " ;
 89             System.out.println( " try : Person name is :  "   +  p.name);
 90              return  p;
 91         }  catch (Exception e) {
 92             e.printStackTrace();
 93         }  finally  {
 94             p.name  =   " person2 " ;
 95             System.out.println( " finally : Person name is :  "   +  p.name);
 96         }
 97        
 98         p.name  =   " person3 " ;
 99         System.out.println( " out : Person name is :  "   +  p.name);
100        
101          return  p;
102      }
103     
104       static   class  Person {
105          public  String name;
106      }
107  }

这样一段代码的结果会是什么呢?

以下是运行结果:

finallyReturnTest :

return value : false

return value : false

finallyBreakTest :

return value : false

return value : false

valueChangeInFinallyTest :

try : i = 100

try : j = 2

finally : i = 1000

finally : j = 3

return value : 100

valueChangeReturnInFinallyTest :

try : i = 100

try : j = 2

finally : i = 1000

finally : j = 3

return value : 1000

refValueChangeInFinallyTest :

try : Person name is : person1

finally : Person name is : person2

return name : person2

 

这个结果很出乎我的意料,我们知道 finally总是会在 try-catch语句块执行完后执行,不管 try语句块中是否已经返回或者抛出了异常。

 

但是在上面的代码测试中,如果 finally语句块中有 return break continue等语句,那么它们会覆盖 try语句块中的 return break continue的语句,如以上的 finallyReturnTest() finallyBreakTest() valueChangeReturnInFinallyTest()三个函数。

另外,如果在 finally语句块中修改要返回的值类型变量的值,则这些修改不会保存下来,如 valueChangeInFinallyTest()函数;如果要返回的值是引用类型,则修改引用类型的内部成员的值会保存下来。

如何解释这个结果呢?

 

问题解释

结合《深入 Java虚拟机(第二版)》这本书和代码编译后产生的二进制指令代码,我对以上问题做了部分解释,鉴于我的才疏学浅,有些观点是有误的,希望高手指正(有误的观点容易引起误导,这也是所以我一直非常小心,奈何水平有限,有些时候难免出错)。

 

在《深入 Java虚拟机(第二版)》的第 18章中提到,在早期的 Java中, finally的行为是通过 JSR指令来实现的,并且为这个指令引入了微型子程序的概念。我的理解,所谓微型子程序就是在函数 A中嵌入一个不完整的函数 B的调用。比如在这本书上的一个例子:

     private   static   int  microSubroutine( boolean  bValue) {
       
try  {
           
if (bValue) {
              
return   1 ;
           }
           
return   0 ;
       } 
finally  {
           System.out.println(
" finally " );
       }
    }

会生成以下的二进制代码:

 0 iload_0

 1 ifeq 11

 4 iconst_1

 5 istore_1

 6 jsr 24

 9 iload_1

10 ireturn

11 iconst_0

12 istore_1

13 jsr 24

16 iload_1

17 ireturn

18 astore_2

19 jsr 24

22 aload_2

23 athrow

 

24 astore_3

25 getstatic #7 <Field java.io.PrintStream out>

28 ldc #1 <String “finally”>

30 invokevirtual #8 <Method void println(java.lang.String)>

33 ret 3

 

如上, 24前缀的代码行以后的部分就是微型子程序,在每一个出口之前都会用 JSR调用这个微型子例程序,在这个微型子例程序返回( ret)后,返回调用 JSR指令的下一条指令,然后返回( ireturn athrow)。

jsr 指令和 ret 指令的格式如下:

jsr    branchbyte1, branchbyte2

把返回地址压栈,跳转至 ((branchbyte1<<8) | branchbyte2)的位置继续之行。

ret index

返回在 index指示的局部变量中存储的值(位置)。

 

在上面的二进制代码中,每次通过 jsr 24跳转到微型子程序,它先将返回地址( jsr 24指令的下一条指令的地址)保存在 index 3的局部变量中,执行完微型子程序后,通过 ret 3返回到调用 jsr 24指令的下一条指令执行,并最终执行返回。

 

可是后来(有人说是自 1.4.2后), JVM中取消了 jsr指令了,所有 finally内部的代码都内联到源代码中了(二进制的源代码)。所以以上的代码在之后的编译器中会产生如下的二进制代码:

     0 iload_0 [bValue]

     1 ifeq 14

     4 getstatic java.lang.System.out : java.io.PrintStream [16]

     7 ldc <String "finally"> [94]

     9 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

    12 iconst_1

13 ireturn

 

    14 getstatic java.lang.System.out : java.io.PrintStream [16]

    17 ldc <String "finally"> [94]

    19 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

    22 iconst_0

23 ireturn

 

    24 astore_1

    25 getstatic java.lang.System.out : java.io.PrintStream [16]

    28 ldc <String "finally"> [94]

    30 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

    33 aload_1

    34 athrow

 

额,貌似有点偏题了,以上的描述是为了解释《深入 Java虚拟机(第二版)》中对 finally描述过时的描述。下面让我们来真正的解决这个问题。还是从生成的 Java二进制代码入手。

 

首先来看一下 valueChangeInFinallyTest()函数的二进制代码(注释了打印语句,使代码简洁):

         //int i = 10

     0 bipush 10

     2 istore_0 [i]

       //int j = 1

     3 iconst_1

     4 istore_1 [j]

       //i = 100

     5 bipush 100

     7 istore_0 [i]

       //j = 2

     8 iconst_2

     9 istore_1 [j]

       // 保存i的值,因为它是要返回的

    10 iload_0 [i]

11 istore 4

//-------------------------------- 内联finally语句块(开始)----------------------

//i = 1000

    13 sipush 1000

16 istore_0 [i]

//j = 3

    17 iconst_3

18 istore_1 [j]

//-------------------------------- 内联finally语句块(结束)----------------------

// 加载保存后的i的值,并返回。这里返回的是finally语句块执行前的i(由istore 4语句缓存起来)的值,因而在finally语句块中任何对i的操作并不会保留下来。这是在没有异常发生的情况下。

    19 iload 4

21 ireturn

 

    22 astore_2 [e]

    23 aload_2 [e]

24 invokevirtual java.lang.Exception.printStackTrace() : void [104]

//-------------------------------- 内联finally语句块(开始)----------------------

    27 sipush 1000

    30 istore_0 [i]

    31 iconst_3

32 istore_1 [j]

//-------------------------------- 内联finally语句块(结束)----------------------

33 goto 45

 

36 astore_3

//-------------------------------- 内联finally语句块(开始)----------------------

    37 sipush 1000

    40 istore_0 [i]

    41 iconst_3

42 istore_1 [j]

//-------------------------------- 内联finally语句块(结束)----------------------

// 而在异常发生但没有被正确处理的情况下,返回值已经没有什么意义了。

    43 aload_3

44 athrow

 

// 这里是在有异常发生,并且异常得到了正确处理的情况下返回的,此时在finally语句块中对i的操作就会保存下来,并返回给调用者。

    45 iload_0 [i]

    46 ireturn

相信以上的注释已经能很好的的解决这个问题了(注:这里 j的存在是为了证明在内联 finally语句块的时候,它只缓存返回值 i,而无须缓存其他变量的值,如 j的值)。需要特别注意的一点是,如果正常返回的话, finally 语句块中修改 i 的值是保存不下来的,但是如果出现异常,并被正常捕获后,在 finally 语句块中修改的 i 的值就会保存下来了。

 

那么对 valueChangeReturnInFinallyTest()函数中的现象如何解释呢?对这个问题解释,首先要理解 ireturn的指令。 ireturn指令没有操作数,它把当前操作栈的栈顶的 int值作为默认的操作数。 ireturn 指令会弹出当前栈顶的 int 值,将其压入调用者的操作栈中,同时忽略当前操作栈中的其他值,即函数正常返回 。因而如果在不优化的情况下,在 finally语句块中的 return语句会返回当前栈顶的 int值(修改后的 i值),然后函数返回,此时栈上的其他操作数就被忽略了,并且原本应该执行的 ireturn语句也不会之行了。这种方式甚至会忽略抛出的异常,即使当前方法有异常抛出,它的调用方法还是认为它正常返回了。

如果查看优化后的 valueChangeReturnInFinallyTest()方法的二进制源码后,会发现当前的代码更加简洁了。但是它还是没有避免在 finally 语句块中使用 return 后,会忽略没有捕获到的异常的问题。

         //int i = 10

     0 bipush 10

     2 istore_0 [i]

       //int j = 1

     3 iconst_1

     4 istore_1 [j]

       //i = 100

     5 bipush 100

     7 istore_0 [i]

       //j = 2

     8 iconst_2

     9 istore_1 [j]

10 goto 22

//catch block

    13 astore_2 [e]

    14 aload_2 [e]

    15 invokevirtual java.lang.Exception.printStackTrace() : void [104]

    18 goto 22

21 pop

//-------------------------------- 内联finally语句块(开始)----------------------

//i = 100

    22 sipush 1000

25 istore_0 [i]

//j = 3

    26 iconst_3

27 istore_1 [j]

//-------------------------------- 内联finally语句块(结束)----------------------

// 返回finally语句块中i的值

    28 iload_0 [i]

    29 ireturn

经过以上的解释,我想对 refValueChangeInFinallyTest()函数中的现象就比较好解释了,因为当进入 finally语句块的时候,保存的只是 Person实例的一个引用,在 finally语句块中依然可以通过引用操作 Person内部成员的,因而在 finally语句块中的修改才能保存下来。

 

而经过编译器优化后的 finallyReturnTest() finallyBreakTest()函数生成的二进制代码就成一样的了:

     0 iload_0 [value]

     1 ifeq 8

     4 goto 8

     7 pop

     8 iconst_0

     9 ireturn

 

1
1
分享到:
评论

相关推荐

    Selenium Essentials 必知必会 最新 原版

    amazon 好评书籍 Get to grips with automated web testing with the amazing power of Selenium WebDriver About This Book ... Finally, you will learn how to perform Data-Driven and Behavior-Driven tests.

    java程序员必知的

    第一,谈谈final, finally, finalize的区别。  final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。finally是异常处理语句结构的一部分,表示总是执行。finalize是 Object类的一个方法...

    09年新书,感知无线电 网络中的频谱接入和管理Dynamic Spectrum Access and Management in Cognitive Radio Networks

    09年新书,很好的知无线电 网络中的频谱接入和管理,下面是IEEE的书评: The book is divided into three parts. Part one is a general introduction to wire- less communication systems, reviewing ...

    Cognitive Computing Architecture,and Intelligent Applications Technologies

    Finally, it describes the representative applications of human-centered cognitive computing, including robot technology, emotional communication system, and medical cognitive system.

    能源大数据管理系统的实现.pdf

    构建更加全面的传感网络,包括温湿度传 感网,气流传感网,做到对用能场所的更全面感 知。可以通过传感器的安装,相当于给机房装了 一个很精准的CT,对机房实现多角度旋转、缩放 观察的虚拟三维清晰图像,温湿度...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    10.4.1 多态——运行方知结果 280 10.4.2 重载也不简单 280 10.4.3 使用多态构建车队 283 10.5 在多态的环境中拨开迷雾 284 10.5.1 神秘的Class类 284 10.5.2 覆盖不再神秘 285 10.5.3 instanceof运算符——让...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    10.4.1 多态——运行方知结果 280 10.4.2 重载也不简单 280 10.4.3 使用多态构建车队 283 10.5 在多态的环境中拨开迷雾 284 10.5.1 神秘的Class类 284 10.5.2 覆盖不再神秘 285 10.5.3 instanceof运算符——让...

    Java的六大问题你都懂了吗

    这些问题对于认真学习java的人都要必知的,当然如果你只是初学者就没必要那么严格了,那如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列。  一、到底要怎么样初始化!  本问题讨论...

    c#学习笔记.txt

    异常处理语句throw, try-catch, try-finally Checked 和 Uncheckedchecked, unchecked fixed 语句Fixed lock 语句Lock (1) foreach 语句为数组或对象集合中的每个元素重复一个嵌入语句组。foreach 语句用于循环访问...

Global site tag (gtag.js) - Google Analytics