java面试题之try中含return语句时代码的执行顺序详解

所属分类: 软件编程 / java 阅读数: 25
收藏 0 赞 0 分享

前言

最近在刷java面试题偶然看到这类问题(try/finally中含有return时的执行顺序),觉得挺有意思于是小小的研究了一下,希望经过我添油加醋天马行空之后,能给你带来一定的帮助,下面来看看详细的介绍。

原题

try {} 里有一个return语句,那么紧跟在这个try后的finally {}里的代码会不会被执行?什么时候被执行?在return前还是后?
乍一看题目很简单嘛,java规范都说了,finally会在try代码块的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;
 }
 }

分析一下

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

但是因为有finally的存在,而finally中对t进行了自增运算,并且finally会在try中的return语句之前执行,所以正确的情况是控制台输出1

所以你最终确定的答案是:控制台输出1

然而事实并非如此,将程序跑起来之后,得到的结果是:

输出0

将栗子跑起来亲眼看一下吧~

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

不用急,到我给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;
 }
 }
  • 当代码执行到return t;时,并不是直接将t返回了出去,而是将t保留了起来(因为还有一个finally语句块没有执行!)并且这个保留,就是值传递性质的一个保留,也就是保留的是t的一个副本(复制体),我这里先叫他tt吧(不是套套!!)
  • 接下来执行finally语句块,finally中将t做了自增运算,t的确变成了1,但是这并没有影响t的复制体tt的值!保留起来的tt值还是0!
  • 这个时候执行完了finally,正式将保留起来的tt返回出去,于是,整个函数的返回结果就是0
  • 这个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才能执行完毕。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

更多精彩内容其他人还在看

Java的面向对象编程基本概念学习笔记整理

这篇文章主要介绍了Java的面向对象编程基本概念学习笔记整理,包括类与方法以及多态等支持面向对象语言中的重要特点,需要的朋友可以参考下
收藏 0 赞 0 分享

Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法

这篇文章主要介绍了Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法 的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

基于Java实现杨辉三角 LeetCode Pascal's Triangle

这篇文章主要介绍了基于Java实现杨辉三角 LeetCode Pascal's Triangle的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

Java中Spring获取bean方法小结

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中获取Spring配置的bean呢?下面通过本文给大家介绍Java中Spring获取bean方法小结,对spring获取bean方法相关知识感兴趣的朋友一起学习吧
收藏 0 赞 0 分享

如何计算Java对象占用了多少空间?

在Java中没有sizeof运算符,所以没办法知道一个对象到底占用了多大的空间,但是在分配对象的时候会有一些基本的规则,我们根据这些规则大致能判断出来对象大小,需要的朋友可以参考下
收藏 0 赞 0 分享

剖析Java中的事件处理与异常处理机制

这篇文章主要介绍了Java中的事件处理与异常处理机制,讲解Java是如何对事件或者异常作出响应以及定义异常的一些方法,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Java的Struts2框架的结构及其数据转移方式

这篇文章主要介绍了详解Java的Struts2框架的结构及其数据转移方式,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

Java封装好的mail包发送电子邮件的类

本文给大家分享了2个java封装好的mail包发送电子邮件的类,并附上使用方法,小伙伴们可以根据自己的需求自由选择。
收藏 0 赞 0 分享

在Java的Struts中判断是否调用AJAX及用拦截器对其优化

这篇文章主要介绍了在Java的Struts中判断是否调用AJAX及用拦截器对其优化的方法,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

java多线程Future和Callable类示例分享

JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。今天我们就来研究下Future和Callab
收藏 0 赞 0 分享
查看更多