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

 

后记

原本以为这是一个小问题的,没想到花了我一个下午的时间才把问题说清楚了,而在描述问题的过程中,我对问题的本质也看的更加清晰了。这个问题开始是源于我在论坛http://www.javaeye.com/topic/458668中看到,感觉论坛里面的人都没很好的说清楚这个问题,刚好我看完了《深入Java虚拟机(第二版)》的书,就把这个问题完整的描述出来了。

                                                                                                                                                                         于2010年9月24日

注:这些文章都是前些时候写的,之前博客很乱,也都是随便贴一些自己写的或转载的,还有一些则是没有贴出来过的。现在打算好好整理一下,完整的记录自己的一些学习历程,而每次看到过去的时间,则让我想起以前的日子,因而我对时间一直是很重视的,所以每篇都著名写的日期,直到最先的文章出现。:)

时间: 2024-08-03 07:22:27

finally知多少的相关文章

Fireeye前副总裁卜峥 :不知攻焉知防,打造“3C的安全体系结构”

一年一度的阿里安全峰会创立于 2014 年,今年已是第三届,于7月13-14日在北京国家会议中心举办.峰会旨在促进亚太区信息安全行业发展,为本地区信息安全组织.信息安全专业人士和决策者搭建一个信息交流展示平台,探讨当前安全行业的最佳实践.热点议题.信息安全人才培养.新 兴技术与发展趋势等.2016 阿里安全峰会设立12个分论坛,数十家领军企业参与.国内外顶级安全专家演讲,在电商金融安全,移动安全,威胁情报,人才培养,电子取证等热门安全行业问题进行深入探讨与交流,除此之外大会前一天还进行了顶级电商

性能测试知多少---系统架构分析

有些事儿一旦放一放就难再拾起来,突然发现<性能测试知多少>这个系列两月没更新,关键时我都不知道啥时候放下的,总容易被各种技术所吸引走,如饥似渴的想学更多的东西,这几天一直有朋友问我为啥不写了,我才意识,事情要一样一样做,我现在要把这个系列完成.   之前有对性能需求进行过分析,那篇主要从项目业务.背景等角度如何抽丝剥茧的将项目的需求抽离出来.在我们进行需求的时候也需要对被测项目的架构有一定的认识,如果不了解被测系统的架构,那么在后期的性能分析与调优阶段将无从下手.   简单系统架构介绍    

如何用SQL语句来判断已知表是否存在

如何判断库中已知表是否存在今日受人之托,帮他解决这个问题,代码为通常的引用Dao做的一模块: Function fExistTable(strTableName As String) As IntegerDim db As DatabaseDim i As Integer Set db = DBEngine.Workspaces(0).Databases(0) fExistTable = False db.TableDefs.Refresh For i = 0 To db.TableDefs.C

app-哪位朋友能告诉我一下:对于36氪、知乎、网易新闻这类APP,详细内容页面是如何获取的

问题描述 哪位朋友能告诉我一下:对于36氪.知乎.网易新闻这类APP,详细内容页面是如何获取的 它们上面的详细页面中的内容是如何获取的,我自己理解的是APP端通过webview从网页端直接获取(而不是通过http连接让服务器端返回数据,然后动态添加到APP端的Imageview和textview来显示图片和内容,我对网页部分开发不是特别了解,如果说是通过webview获取的,那么有没有什么格式要求.求大神解答 解决方案 如果是直接获取,就一定有相应的网址吧.如果有网址,那么在 IE 浏览器中也应

使用Scrapy爬取知乎网站

本文主要记录使用使用 Scrapy 登录并爬取知乎网站的思路.Scrapy的相关介绍请参考 使用Scrapy抓取数据. 相关代码,见 https://github.com/javachen/scrapy-zhihu-github ,在阅读这部分代码之前,请先了解 Scrapy 的一些基本用法. 使用cookie模拟登陆 关于 cookie 的介绍和如何使用 python 实现模拟登陆,请参考python爬虫实践之模拟登录. 从这篇文章你可以学习到如何获取一个网站的 cookie 信息.下面所讲述

网站开发人员应该知道的61件事

有人在Stack Overflow上发问,动手开发网站之前,需要知道哪些事情? 不出意料地,他得到了一大堆回答. 通常情况下,你需要把所有人的发言从头到尾读一遍.但是,Stack Overflow有一个很贴心的设计,它允许在问题下方开设一个wiki区,让所有人共同编辑一个最佳答案.于是,就有了下面这篇文章,一共总结出六个方面共计61条"网站开发须知". 我发现,这种概述性的问题,最适合这种集合群智.头脑风暴式的回答方式了.这也是我第一次觉得,Stack Overflow做到了Wikip

百度地图 js 通过IP定位城市后,已知一个坐标集合,根据每个坐标添加标注点,求大神help me

问题描述 百度地图 js 通过IP定位城市后,已知一个坐标集合,根据每个坐标添加标注点,求大神help me var myCity = new BMap.LocalCity(); // 以当前IP定位到城市 myCity.get(myFun); // 根据IP对当前城市进行定位 function myFun(result){ var center = result.center; // 城市坐标中心点 var point = new BMap.Point(center.lng,center.la

关于WordPress需要知道的100件事:主题篇

主题篇-"> 这篇文章是关于WordPress你需要知道的100件事系列的第二部分,关于WordPress主题你需要知道的十件事. 1. 不要在搜索引擎上搜索免费WordPress主题 从搜索引擎上可以搜到各种各样的免费主题,不过它们可能带有spamming链接,也可能已经被恶意软件感染. 最好选择来自WordPress官方主题库的免费插件. 2. 网页设计越专业,为网站带来的商业转换率越高 可用性研究表明,网站图样与版式越专业,读者对网站的认知价值和信任度越高. 选择主题时,尽量选择看起

中国版Quora:知乎

知乎是一个极具中国传统特色的词,知是指知道或知识,而乎是中国古代最常用的语气助词与介词,在历朝各代的古文.诗词中极为常见.饱受文言文教育压榨的我们自然是再熟悉不过了,在<史记.陈涉世家>中就有"王侯将相宁有种乎!"一段. 同时知乎也是一个社会化问答网站的名称,被誉为是国外Quora的中国版,这个网站在一月底上线测试,目前仍然处于严格的邀请控制中.与百度知道这样的传统问答网站相比,知乎更强调人的元素与知识性,相比起来社会化问答更像是介于百科与传统问答网站之间. 百科类网站的词

SQL Server 2005:你应该知道的13件事情

距离微软的SQL Server 2005正式版的推出,已经将近一年的时间.随着最近两份研究报告的出炉,SQL Server 2005又引起了业界的关注和评论--微软凭借SQL Server 2005取得了超过Oracle和IBM数据库产品市场占有率的增长速度.但是,对于不足一岁的SQL Server 2005,还有一些你应该知道的事情,在本文中列举了关于SQL Server 2005你应该知道的13件事情. SQL Server 2005新增的功能 1. 新的产品版本 除SQL Server 2