美文网首页
JAVA谈谈当return提前返回时的finally

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

作者: K807 | 来源:发表于2018-06-04 18:18 被阅读0次

常见的一个完整的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块可以改变返回对象的状态

相关文章

网友评论

      本文标题:JAVA谈谈当return提前返回时的finally

      本文链接:https://www.haomeiwen.com/subject/ncjysftx.html