面试题思考:try 代码块中含 return 语句时,代码执行顺序

刷java面试题偶然看到这类问题(try/finally中含有return时的执行顺序),以为挺有意思因而小小的研究了一下,但愿通过我添油加醋天马行空以后,能给你带来必定的帮助javascript

原题

try {} 里有一个return语句,那么紧跟在这个try后的finally {}里的代码会不会被执行?何时被执行?在return前仍是后? java

乍一看题目很简单嘛,java规范都说了,finally会在try代码块的return以前执行,你这文章写得没意义,不看了git

你等等!(拿起我身边的五尺砍刀)github

神奇栗子

看完这个栗子,你在想一想执行顺序究竟是怎样的面试

栗子代码

public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        int t = 0;
        try {
            return t;
        } finally {
            ++t;
        }
    }复制代码

分析一下

test()方法内,在try中return了t,那么在main方法中test()函数的返回值应该是t=0,即控制台输出0函数

可是由于有finally的存在,而finally中对t进行了自增运算,而且finally会在try中的return语句以前执行,因此正确的状况是控制台输出1ui

因此你最终肯定的答案是:控制台输出1搜索引擎

然而事实并不是如此,将程序跑起来以后,获得的结果是:
输出0
将栗子跑起来亲眼看一下吧~spa

获得这个结果你也许要爆炸了,啥?java规范说的都是错的?!code

不用急,到我给sun洗地的时间了

洗地时间

在洗地以前,你颇有必要先理解java中的值传递,若是你已经了解该内容可略过下面这一个小节点

java中的值传递

因为这只是本文内容引伸出去的知识点,不过多赘述,随便唠两句,能借此明白则好,不明白但愿借助搜索引擎明白一下!

在java的方法调用中,时常须要传递参数,那么传递的参数是将以前的变量直接传递给方法内了吗?

显然不是的,调用方法传递参数的时候,传递的只是原变量的一个副本(复制体),换句话说就是,将变量的值传递给了方法体,而并无真正的将变量传递进去。

看个栗子:

public static void main(String[] args) {
        int t = 0;
        test(t);
        System.out.println(t);
    }

    public static int test(int a) {
        a = 111;
    }复制代码

正确输出是0,由于test()方法内拿到的a,只是t的一个副本(复制体)而并不直接是t,test()内改变了a的值,并不影响t的值

以上是对于基本数据类型,若是对于对象呢?

若是参数是对象,那么传递的是对象的引用副本(复制体),这也就是为何在方法体内对对象进行修改,会真正的改变对象。由于方法体外的引用和方法体内的引用指向的是堆内存中的同一个对象,传递的是对象的引用

若是这里还不能理解值传递,建议先理解一下这一个概念再继续往下看

真的开始分析了

为了你看着方便,栗子代码再来一份:(我真的不是为了凑字数)

public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        int t = 0;
        try {
            return t;
        } finally {
            ++t;
        }
    }复制代码
  1. 当代码执行到return t;时,并非直接将t返回了出去,而是将t保留了起来(由于还有一个finally语句块没有执行!)
    而且这个保留,就是值传递性质的一个保留,也就是保留的是t的一个副本(复制体),我这里先叫他tt吧(不是套套!!)
  2. 接下来执行finally语句块,finally中将t作了自增运算,t的确变成了1,可是这并无影响t的复制体tt的值!保留起来的tt值仍是0!
  3. 这个时候执行完了finally,正式将保留起来的tt返回出去,因而,整个函数的返回结果就是0
  4. 这个t的副本(复制体)保留的地方是哪儿呢?我查了半天,有个应该靠谱的说法,保留在函数栈中,但具体保留的区域叫什么,我也不知道,还请知情大佬指教一下!

上图或许直观一点?

灵魂画师

叫我一声灵魂画师我可敢答应!

那么若是,这个t是一个对象呢?按照前面说的值传递的问题,若是t是一个对象,在finally中对t进行修改,那么最终返回出去的t所显示出来的数据,应该是通过修改的。

写一个Person类来检验一下吧

public class Test {

    public static void main(String[] args) {
        Person result = test();
        System.out.println(result.age);
    }

    public static Person test() {
        Person t = new Person();
        t.age = 0;
        try {
            return t;
        } finally {
            t.age++;
        }
    }

}

class Person {
    int age;
}复制代码

这段代码输出的是1,由于Person是一个类,t是一个对象的引用,对象实例保存在堆内存中,t的副本tt也是一个对象的引用,t和tt都指向堆内存中的对象实例,那么不论修改谁,实际上对象实例都被修改了!

看完我这一通胡说八道,你应该了解了整个执行流程咯?

那么继续开一个引伸

又一个小栗子

若是在finally中也有一个return,会发生什么?

public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        int t = 0;
        try {
            return t;
        } finally {
            ++t;
            return t;
        }
    }复制代码

最终输出的结果是1

就是说,若是try中有return而finally中也有return,那么后者将会让前者失效!

理解

=> try中将t保留了一份副本用于返回出去,到了finally中,又有一个return语句,这时候又要建立一个用于返回的副本,那这个时候就有两个副本了,到底返回谁呢?取后者!

总结

这一个面试题,看似简单,却暗藏杀机啊!

但是说了这么多,结果就是finally在return以后执行吗?

非也,你没看见return没有真正的执行完就开始执行finally吗?而且是先执行完了finally,才执行完return,这也就很好理解java规范中的finally在return以前执行了。

不过,按如上状况,这句话应该变成这样:finally比return先执行完毕。是否是就更容易理解了呢?
也就是说,return先被执行了,执行return的时候发现有finally,因而不能那么快执行完毕return,先去执行finally,等finally执行完毕以后,return才能执行完毕。

全文下来,真是用个人三寸不烂之舌通过口若悬河的输出连绵不绝的蛊惑打开了你的新世界大门啊

结语

更多内容欢迎访问个人主页个人博客若是个人文章确实有帮助到你,请不要忘了点一下文末的"♡"让他变成"❤"做为一直雏鸡不免不少地方理解不到位,文中如有错误请直(bu)接(yao)指(ma)出(wo)写做不易!

相关文章
相关标签/搜索