`
helloyesyes
  • 浏览: 1306692 次
  • 性别: 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
分享到:
评论

相关推荐

    36篇JAVA文档(不为人知的问题及分析+程序员必知要点)

    这份包含36篇文章的文档集合,旨在深入剖析Java中的不为人知的问题,提供程序员必须掌握的关键知识点,以及如何应对面试中可能遇到的问题。下面将详细讨论这些文档可能涵盖的内容。 1. **内存管理与垃圾回收**:...

    Android中高级面试必知必会.zip

    - **异常处理**:理解Checked和Unchecked异常的区别,以及如何合理使用try-catch-finally语句。 2. **Android相关**: - **Activity生命周期**:掌握各状态转换,理解onCreate()、onStart()、onResume()、onPause...

    java程序员必知的

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

    Android中高级面试必知必会.pdf

    目录 Android 中高级面试必知必会.1 第一章 Java 相关高频面试解析..........3 1. HashMap ......3 2. ArrayList .....25 3.LinkedList.........30 4.Hashset 源码分析........35 5. 内存模型.......47 6. 垃圾回收...

    C#小知識點

    `try`块中捕获异常,`catch`块处理异常,`finally`块确保即使发生异常也能执行的清理代码。 10. **using关键字**:用于确保资源在使用后得到正确释放,常用于 Dispose模式。例如: ```csharp using ...

    Java面试必知必会Gothic主题.rar

    "Java面试必知必会Gothic主题"可能包含了一系列与Java核心技术、面试技巧以及常见问题相关的资料。下面我们将深入探讨一些Java面试中常见的核心知识点。 1. **基础语法**:面试通常会从Java的基础开始,如数据类型...

    VB.NET理論知識

    8. **错误处理和异常处理**:VB.NET的Try...Catch...Finally结构用于捕获和处理程序运行时的错误。异常是程序运行时发生问题的表示,正确处理异常可以确保程序的稳定性和健壮性。 9. **泛型**:VB.NET的泛型允许...

    通过这篇总结实现找工作自由! 《Java面试必知必系列-V1.0》靠谱

    本文将基于《Java面试必知必系列-V1.0》的指导,提炼出一些关键知识点。 1. **Java基础** - **三大特性**:封装、继承和多态。封装是限制对对象属性的直接访问,通过方法来操作;继承允许创建新的类(子类)继承已...

    500道java后端面试必知必会

    2. **异常处理**: 学习如何使用try-catch-finally语句来捕获和处理运行时错误,以及了解不同类型的异常,如Checked异常和Unchecked异常的区别。 3. **内存管理与垃圾回收**: 了解Java的内存模型,包括堆内存、栈...

    500道Java后端面试必知必会-V1版.pdf

    2. 异常处理:理解和掌握Java的异常处理机制,包括try-catch-finally语句的使用,了解受检异常和非受检异常的区别,以及如何自定义异常。 3. 面向对象编程:深入理解Java的面向对象特性,例如类和对象的概念、继承...

    VB6.0习题集小测试+答案,很不错的小知,可以巩固我们的学习知识.

    7. **错误处理**:VB6.0提供了On Error语句进行错误处理,学习者应理解如何使用Try...Catch...Finally结构来捕获和处理运行时错误。 8. **用户界面设计**:VB6.0的集成开发环境(IDE)提供了直观的拖放式设计,使得...

    大数据岗位必知必会的53个Java基础

    - `finally` 保证代码块一定会执行,通常用于异常处理。 - `finalize` 是对象被垃圾回收前调用的方法,用于清理资源,但不保证一定会执行。 7. **序列化 `Serializable` 和 `Parcelable`**: - `Serializable` ...

    2019_2020学年高中英语Module2TrafficJam5SectionⅤWriting知能演练轻松闯关外研版必修4

    3. **词汇运用**:第5句"If you have a job, do devote yourself to it and finally you’ll succeed."强调了副词"do"在强调动词时的用法,同时提醒学生要全身心投入工作才能成功。第6句"Please do me a favor—...

    2018八年级英语上册Unit8HowdoyoumakeabananamilkshakeSelfCheck知能演练提升新版人教新目标版

    2. `Then/Next/Finally`: 这些词常用于指示步骤顺序,描述做某事的流程。 3. `Do you know how...?`: 你知道怎么... 这是一个询问对方是否知道某个信息的问句结构。 **阅读填词** 1. `eat`: 吃。 2. `add`: 添加。...

    英语经典表达.docx

    - 这里使用了 “as far as we are aware” 来强调目前所知的信息。 - **方法**:“First of all, we synthesized the modified particles. Then, we used these particles to create the imprinted molecular ...

    2019_2020学年高中英语Unit1Livingwell4SectionⅣGrammar知能演练轻松闯关新人教版选修7

    - "Amy managed to fly across many countries and finally reached Australia."(艾米设法飞过了许多国家,最终到达了澳大利亚。)"to fly"在这里是动词不定式,但在"manage to do"结构中,"to"后的动词通常用原形...

    广东省广州市2018年中考英语学科模拟题一20180709282

    "I finally managed __3__ one..." "manage to do sth." 表示成功地做了某事,因此这里应该填"to get",答案是C。 第四题考查物主代词。"...in __4__ new home all the time." 这里的"new home"指的是鸟的新家,...

Global site tag (gtag.js) - Google Analytics