java中finally与return的执行顺序详解

时间:2020-12-27 20:18:24
一些准备知识:
    首先为了说明白java中finally与return的执行顺序是怎样的这个问题,我们需要做一点准备工作。
    java方法是在栈幀中执行,栈幀是线程私有栈的单位,执行方法的线程会为每一个方法分配一小块栈空间来作为该方法执行时的内存空间,栈幀分为三个区域:
    1. 操作数栈,用来保存正在执行的表达式中的操作数,数据结构中学习过基于栈的多项式求值算法,操作数栈的作用和这个一样
    2. 局部变量区,用来保存方法中使用的变量,包括方法参数,方法内部声明的变量,以及方法中使用到的对象的成员变量或类的成员变量(静态变量),最后两种变量会复制到局部变量区,因此在多线程          环境下,这种变量需要根据需要声明为volatile类型
    3. 字节码指令区,这个不用解释了,就是方法中的代码翻译成的指令
return语句:
    准备知识讲完了,在本节中我们讲解方法中没有finally语句块的情况下return语句的执行方式。现在我们先看看return语句,return语句的格式如下:
    return [expression];
    其中expression(表达式)是可选的,因为有些方法没有返回值,所以return后面也就没有表达式,或者可以看做是空的表达式。
    我们知道return语句的作用可以结束方法并返回一个值,那么他返回的是哪里的值呢?返回的是return指令执行的时刻,操作数栈顶的值,不管expression是一个怎样的表达式,究竟做了些什么工作,对于return指令来说都不重要,他只负责把操作数栈顶的值返回。
    而return expression是分成两部分执行的:
    执行:expression;
    执行:return指令;
    例如:return x+y;
    这句代码先执行x+y,再执行return;首先执行将x以及y从局部变量区复制到操作数栈顶的指令,然后执行加法指令,这个时候结果x+y的值会保存在操作数栈的栈顶,最后执行return指令,返回操作数栈顶的值。
    对于return x;先执行x,x也是一个表达式,这个表达式只有一个操作数,会执行将变量x从局部变量区复制到操作数栈顶的指令,然后执行return,返回操作数栈顶的值。因此return x实际返回的是return指令执行时,x在操作数栈顶的一个快照或者叫副本,而不是x这个值。
finally语句块:
    如果方法中有finally语句块,那么return语句又是如何执行的呢?
    例如下面这段代码:
     try {
     return  expression;
  } finally {
     do  some work;
  }
   首先我们知道,finally语句是一定会执行,但他们的执行顺序是怎么样的呢?他们的执行顺序如下:
    1、执行:expression,计算该表达式,结果保存在操作数栈顶;
    2、执行:操作数栈顶值(expression的结果)复制到局部变量区作为返回值;
    3、执行:finally语句块中的代码;
    4、执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
    5、执行:return指令,返回操作数栈顶的值;
    我们可以看到,在第一步执行完毕后,整个方法的返回值就已经确定了,由于还要执行finally代码块,因此程序会将返回值暂存在局部变量区,腾出操作数栈用来执行finally语句块中代码,等finally执行完毕,再将暂存的返回值又复制回操作数栈顶。所以无论finally语句块中执行了什么操作,都无法影响返回值,所以试图在finally语句块中修改返回值是徒劳的。因此,finally语句块设计出来的目的只是为了让方法执行一些重要的收尾工作,而不是用来计算返回值的。
如果还有不懂的话可以看看示例代码
public  class  FinallyDemo {
     
     public  int  testMethod(String _int,String _className){
         
         int  x =  1 ;
         
         try {
             Integer.valueOf(_int);
             Class.forName(_className);
             //如果上面两句代码没有发生异常,对于return x这句代码,程序会先计算表达式x
             //即将x从局部变量区复制到操作数栈顶,结果就是操作数栈顶的值,也就是x的值,为1
             //然后将操作数栈顶的值复制到局部变量区(假设这个复制到局部变量区的值叫returnvalue),再执行finally代码块,在finally代码块
             //中,x的值被修改为3(即局部变量区中的x值),finally执行完,程序又将returnvalue复制到操作数栈顶,然后执行return指令,返回
             //操作数栈顶的值,最终返回值是1
             return  x;
         } catch (ClassNotFoundException e){
             //同样发生了ClassNotFoundException,x的值被修改成2
             //因此在catch中的return x语句中定义了返回值大小为2,所以最终返回的是2
             x =  2 ;
             return  x;
         } finally {
             //这句代码一定会执行的,这里的代码执行结束后才会结束方法并返回值,因此在finally语句修改x不会影响返回值,因为返回值在return 后面的
             //表达式执行完就已经确定了,他是x的快照,而不是x
             x =  3 ;
         }
     }
     
     public  static  void  main(String [] args){
         try {
             FinallyDemo demo =  new  FinallyDemo();
             int  i_1 = demo.testMethod( "123123" "expert.in.java.lang.CalendarDemo" );
             System.out.println(i_1); //结果为1
             int  i_2 = demo.testMethod( "123123" "com.edu.cn.qj.MyClass" );
             System.out.println(i_2); //结果为2
             int  i_3 = demo.testMethod( "dsadf" "expert.in.java.lang.CalendarDemo" );
             System.out.println(i_3); //没有返回值,会抛出异常
         } finally {
             
         }
     }
}