JAVA谈谈当return提前返回时的finally

常见的一个完整的try catch finally结构语句是这样的

try{}
catch(){}
finally{}

有一点基础的读者都会对finally有一点理解:

  1. 如果在try块,catch块中return,会执行完finally块语句后再返回
  2. 如果在try块,catch块中抛出了未受检查的异常,会执行完finally块语句后再抛出异常

基于这两点,如果程序到达try块,那么finally块的代码则一定会执行,所以finally块常用来安全的清理资源,锁释放。

但是finally还有一些细节需要小心,不然可能会得到你意想不到的结果
我们先上一段代码,请读者猜测命令行输出的结果

public class TestClass {
    public int m1(){
        int x;
        try{
            x = 1;
            throw new Exception();
        } catch (Exception e){
            x = 2;
            return x;
        }finally {
            x = 3;
        }
    }

    public int[] m2(){
        int[] x = new int[2];
        try{
            x[0] = 1;
            throw new Exception();
        } catch (Exception e){
            x[1] = 2;
            return x;
        }finally {
            x[1] = 3;
        }
    }

    public String m3(){
        String x = null;
        try{
            x = "1";
            throw new Exception();
        } catch (Exception e){
            x = "2";
            return x;
        }finally {
            x += "3";
        }
    }

    public static void main(String[] args) {
        TestClass tc = new TestClass();
        System.out.println("m1():" + tc.m1());
        System.out.println("m2()[1]:" + tc.m2()[1]);
        System.out.println("m3():" + tc.m3());
    }
}

最后的返回值是

m1():2
m2()[1]:3
m3():2

如果返回值和你预期的一样,那么说明你已经理解了我将要说的内容,可以不用继续往下看了

首先来看第一个方法

    public int m1(){
        int x;
        try{
            x = 1;
            throw new Exception();
        } catch (Exception e){
            x = 2;
            return x;
        }finally {
            x = 3;
        }
    }

或许有的读者预期的结果是

m1():3

不是说finally块一定会执行吗,为什么x的值没有发生变化,finally块确实执行了,但是为什么x还是等于2
这里就是我今天要说的第一点,当try块或者catch块中提前返回时,finally块的语句不会改变return的返回值
具体来看catch块和finally块的字节码,不想看字节码,想直接看结果的可以跳过这个部分

字节码分析 ↓↓

//catch块
// x=2
11: iconst_2
12: istore_1
13: iload_1
14: istore_3
//finally块
// x=3
15: iconst_3
16: istore_1
17: iload_3
//return
18: ireturn

为了便于理解,我先给出局部变量表1和3位置的含义(不准确,因为局部变量表是可以复用的,因此可能这个时候代表的是变量x下一个时刻代表y,但这个例子里局部变量表没有被复用)

下标 0 1 2 3
含义 int x int returnValue

字节码的部分我简单解释一下
第11、12的意思就是将数字2存到局部变量表1的位置
第13、14行的意思是从局部变量表中1的位置读取数字2,然后再将数字2存到局部变量表3的位置
此时局部变量表的数值为

下标 0 1(x) 2 3(returnValue)
数值 int 2 int 2

第15、16行的意思就是将数字3存到局部变量表1的位置
此时局部变量表的数值为

下标 0 1(x) 2 3(returnValue)
数值 int 3 int 2

第17、18行的意思就是读取局部变量表3位置的数字作为返回值返回

因此这里看出,当语句要执行finally块时,它先把准备返回的数值2临时保存在了3这个位置,当执行完finally块之后,回来把3位置的值给返回了

字节码分析 ↑↑

我分析一下过程:

  1. 首先程序捕获到异常,进入了catch块
  2. 执行到x=2之后,想继续执行return方法直接返回
  3. 这时候finally语句块说,你等等,我还有方法没执行,先不要返回,这时return语句想,虚拟机要求我必须执行finally,那好吧,我去执行finally语句块,我把准备返回的值2先存放在returnValue里面,等我执行完了再回来返回
  4. 执行完finally块
  5. return回来,把刚才暂放在returnValue的值2取出来返回

由上面可以看出,finally不管执行什么语句,到最后return总是返回已经提前准备好的returnValue,finally语句不会改变catch块中的返回值

接下来我们来看第二个方法的代码

    public int[] m2(){
        int[] x = new int[2];
        try{
            x[0] = 1;
            throw new Exception();
        } catch (Exception e){
            x[1] = 2;
            return x;
        }finally {
            x[1] = 3;
        }
    }

这里输出的x[1] 为什么又是3而不是2了呢,不是说finally不会改变return的返回值吗

m2()[1]:3

这里是我要说的第二点,JAVA中只有值传递,没有引用传递,对于引用类型(例如数组,对象),仍然也是值传递,只是这个值传递的是对象的地址
那我们继续按照刚才的思路,当return准备返回时,先去执行finally块语句,并且把准备返回的值保存在了retunValue里面


这时returnValue保存的是数组的首地址:0XABCDEF,最后返回值0XABCDEF时不变的,但是可以改变对象的状态,使数组int[1]的更改为3。

此时返回的仍然是0XABCDEF,但数组对象的状态已经被改变了。

接下来我们来看第三个方法的代码

    public String m3(){
        String x = null;
        try{
            x = "1";
            throw new Exception();
        } catch (Exception e){
            x = "2";
            return x;
        }finally {
            x += "3";
        }
    }

综合以上两点,有经验的读者可能已经明白了,为什么m3方法返回的是2而不是12

m3():2

这里就是我的说的第三点,不可变类型只能改变引用不能改变状态,所谓不可变类型不仅是用final修饰的类,还要求类的域是不可变的,对于String类

x += "3";

这一步会被分解为三个操作

  1. 获取x的值"1"
  2. 生成一个新的String对象,对象的值为"1" + "2"即"12"
  3. 将新的String对象的引用替换x的引用

在这个方法中,首先是会将x的地址保存到returnValue


而finally的语句x+= "3"会生成一个新的String对象,并将x的引用指向它


最后return返回的returnValue仍然是"2"这个字符串

总结

最后总结一下,当在

try{}
catch(){return}
finally{}

try{return}
catch(){}
finally{}

的结构中

  1. finally块的代码一定会执行
  2. finally块的代码不会改变try块catch块的返回值
  3. 如果try块catch块return的是引用类型的地址,finally块可以改变返回对象的状态
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355