首页 > 试题广场 >

try括号里有return语句, finally执行顺序

[单选题]
try括号里有return语句, finally执行顺序
  • 不执行finally代码
  • return前执行
  • return后执行
推荐
下面的关键内容来自:
是关于 try  return  finally  的详细解释文档,很有说服力。

清单 5.
 public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
	 } 

 public static int getValue() { 
        try { 
                 return 0; 
        } finally { 
                 return 1; 
	        } 
	 } 
 }

清单 5 的执行结果:

 return value of getValue(): 1
清单 6.
 public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
	 } 

 public static int getValue() { 
        int i = 1; 
        try { 
                 return i; 
        } finally { 
                 i++; 
        } 
	 } 
 }

清单 6 的执行结果:

 return value of getValue(): 1

利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。 由此,可以轻松的理解清单 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。那为什么清单 6 的返回值不是 2,而是 1 呢?按照清单 5 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?

关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

是不是不太好理解,那我们就用具体的例子来做形象的说明吧!

为了能够解释清单 6 的执行结果,我们来分析一下清单 6 的字节码(byte-code):

 Compiled from "Test.java"
 public class Test extends java.lang.Object{ 
 public Test(); 
  Code: 
   0: 	 aload_0 
   1:invokespecial#1; //Method java/lang/Object."<init>":()V 
   4: 	 return 

  LineNumberTable: 
   line 1: 0 

 public static void main(java.lang.String[]); 
  Code: 
   0: 	 getstatic 	 #2; //Field java/lang/System.out:Ljava/io/PrintStream; 
   3: 	 new 	 #3; //class java/lang/StringBuilder 
   6: 	 dup 
   7: 	 invokespecial 	 #4; //Method java/lang/StringBuilder."<init>":()V 
   10: 	 ldc 	 #5; //String return value of getValue(): 
   12: 	 invokevirtual 	 
   #6; //Method java/lang/StringBuilder.append:(
       Ljava/lang/String;)Ljava/lang/StringBuilder; 
   15: 	 invokestatic 	 #7; //Method getValue:()I 
   18: 	 invokevirtual 	 
   #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 
   21: 	 invokevirtual 	 
   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
   24: 	 invokevirtual 	 #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
   27: 	 return 

 public static int getValue(); 
  Code: 
   0: 	 iconst_1 
   1: 	 istore_0 
   2: 	 iload_0 
   3: 	 istore_1 
   4: 	 iinc 	 0, 1 
   7: 	 iload_1 
   8: 	 ireturn 
   9: 	 astore_2 
   10: 	 iinc 	 0, 1 
   13: 	 aload_2 
   14: 	 athrow 
  Exception table: 
   from   to  target type 
     2     4     9   any 
     9    10     9   any 
 }

对于 Test()构造方法与 main()方法,在这里,我们不做过多解释。让我们来分析一下 getValue()方法的执行。在这之前,先让我把 getValue()中用到的虚拟机指令解释一下,以便读者能够正确的理解该函数的执行。

1.	iconst_ 
Description: Push the int constant  (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.
Forms: iconst_m1 = 2 (0x2)  iconst_0 = 3 (0x3)  iconst_1 = 4 (0x4)  
iconst_2 = 5 (0x5) iconst_3 = 6 (0x6)  iconst_4 = 7 (0x7)  iconst_5 = 8 (0x8)

2.	istore_ 
Description: Store int into local variable. The  must be an index into the 
local variable array of the current frame. 
Forms: istore_0 = 59 (0x3b)  istore_1 = 60 (0x3c)  istore_2 = 61 (0x3d)  
istore_3 = 62 (0x3e)

3.	iload_ 
Description: Load int from local variable. The  must be an index into the 
local variable array of the current frame. 
Forms: iload_0 = 26 (0x1a)  iload_1 = 27 (0x1b)  iload_2 = 28 (0x1c)  iload_3 = 29 (0x1d)

4.	iinc index, const 
Description: Increment local variable by constant. The index is an unsigned byte that 
must be an index into the local variable array of the current frame. The const is an 
immediate signed byte. The local variable at index must contain an int. The value 
const is first sign-extended to an int, and then the local variable at index is 
incremented by that amount.
Forms:  iinc = 132 (0x84)

Format:
iinc 	
index 	
const 	

5.	ireturn 
Description: Return int from method.
Forms:  ireturn = 172 (0xac)

6.	astore_ 
Description: Store reference into local variable. The  must be an index into the 
local variable array of the current frame.
Forms: astore_0 = 75 (0x4b) astore_1 = 76 (0x4c) astore_2 =77 (0x4d) astore_3 =78 (0x4e)

7.	aload_ 
Description: Load reference from local variable. The  must be an index into the 
local variable array of the current frame.
Forms: aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d)

8.	athrow 
Description: Throw exception or error.
Forms: athrow = 191 (0xbf)

有了以上的 Java 虚拟机指令,我们来分析一下其执行顺序:分为正常执行(没有 exception)和异常执行(有 exception)两种情况。我们先来看一下正常执行的情况,如图 1 所示:

图 1. getValue()函数正常执行的情况

由上图,我们可以清晰的看出,在 finally 语句块(iinc 0, 1)执行之前,getValue()方法保存了其返回值(1)到本地表量表中 1 的位置,完成这个任务的指令是 istore_1;然后执行 finally 语句块(iinc 0, 1),finally 语句块把位于 0 这个位置的本地变量表中的值加 1,变成 2;待 finally 语句块执行完毕之后,把本地表量表中 1 的位置上值恢复到操作数栈(iload_1),最后执行 ireturn 指令把当前操作数栈中的值(1)返回给其调用者(main)。这就是为什么清单 6 的执行结果是 1,而不是 2 的原因。

再让我们来看看异常执行的情况。是不是有人会问,你的清单 6 中都没有 catch 语句,哪来的异常处理呢?我觉得这是一个好问题,其实,即使没有 catch 语句,Java 编译器编译出的字节码中还是有默认的异常处理的,别忘了,除了需要捕获的异常,还可能有不需捕获的异常(如:RunTimeException 和 Error)。

从 getValue()方法的字节码中,我们可以看到它的异常处理表(exception table), 如下:

Exception table:

from to target type

2 4 9 any

它的意思是说:如果从 2 到 4 这段指令出现异常,则由从 9 开始的指令来处理。

图 2. getValue()函数异常执行的情况

先说明一点,上图中的 exception 其实应该是 exception 对象的引用,为了方便说明,我直接把它写成 exception 了。

由上图(图 2)可知,当从 2 到 4 这段指令出现异常时,将会产生一个 exception 对象,并且把它压入当前操作数栈的栈顶。接下来是 astore_2 这条指令,它负责把 exception 对象保存到本地变量表中 2 的位置,然后执行 finally 语句块,待 finally 语句块执行完毕后,再由 aload_2 这条指令把预先存储的 exception 对象恢复到操作数栈中,最后由 athrow 指令将其返回给该方法的调用者(main)。

通过以上的分析,大家应该已经清楚 try-catch-finally 语句块的执行流程了吧!
上面的内容来自复制粘贴,解释很有说服力,特粘贴给大家。
try  return  finally  的详细解释文档:
https://www.ibm.com/developerworks/cn/java/j-lo-finally/
编辑于 2016-11-03 09:12:12 回复(25)
选项B;我相信仔细看的话,每一本Java书都有讲过。“假设利用 return 语句从 try 语句块中退出。在方法返回前,finally子句的内容将被执行。如果 finally 子句中也有一个 return 语句,这个返回值将会覆盖原始的返回值。”
public static void main(String[] args) {
        int k = f_test();
        System.out.println(k);
    }
    
    public static int f_test(){
        int a = 0;
        try{
            a = 1;
            return a;
        }
        finally{
            System.out.println("It is in final chunk.");
            a = 2;
            return a;
        }
    }
输出: 
It is in final chunk.
2

如果将 return a; 注释掉,将输出
It is in final chunk.
1        
编辑于 2016-05-23 21:57:53 回复(19)
出题的也是不严谨,或者根本不了解,看编译之后的字节码就知道了,确切的说是在return执行的中间。应该增加一个选项,D,return执行到中间的时候发现finally,则先不返回,把值暂存到本地栈,等finally运行之后,如果finally里有返回语句,那么以finally为主,否则才把本地栈里的返回值真的返回。
发表于 2016-10-03 12:30:34 回复(8)
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
发表于 2015-10-26 21:54:48 回复(3)
应该是,先计算return 里的值,然后,去执行finally里的代码,再回到return 来返回。
那这怎么算呢。。。。
发表于 2015-04-06 16:50:28 回复(6)
从执行结果看确实像是“先return,再finally”,然而如果看生成的字节码,其实是在try块结束时,将返回值存在本地变量表中,然后执行finally,最后从本地变量表取出返回值并进行return动作。因此,本题强调“在哪里执行finally里的代码”,感觉应该选B

发表于 2015-08-10 14:17:54 回复(4)
B
    finally—在异常处理时提供 finally 块来执行任何清除操作,如果抛出一个异常,那么相匹配的 catch 子句就会执行。然后控制就会进入 finally 块(如果有的话)。如果try/catch里遇到return语句,则finally 里面的东西在return之前执行。
    如果try/catch里调用其他函数,该函数有return语句,则finally 里面的东西在该函数的return之前执行。
    总之,如果有return语句,则finally里的内容始终会在return语句之前执行。


发表于 2015-03-28 16:36:53 回复(0)
相信我这样说大家肯定能理解, Java无论如何都是顺序执行的,当try中有return语句,比如 return x, Java会把x先放进一个内存中,并不真正返回,而是再执行finally中的语句,当执行完finally之后,才会真正把内存中的x返回 也许有人会问道,如果此时finally中也有return怎么办,如return y,此时会把y替换之前的x,,然后才是真正的返回
发表于 2017-05-03 00:50:44 回复(1)
《java核心技术卷一》是这么写的

1.如果try中包含return,finally{}块中的内容,将会在return之前执行
2.如果finally中也包含return ,如果修改了try中的返回值值,这个返回值将会被修改。
发表于 2018-09-05 15:16:41 回复(0)
yql头像 yql
    首先 ,执行就意味着跳出函数(除非碰到exit函数!!),所以任何语句要执行都要在return之前执行!!但是貌似这个没什么说服力T..T
    对于题目中括弧里面的解释:当finally语句中有return时,会覆盖掉函数中其他return语句。此外,,由于一个方法内部定义的变量都存储在栈中,当这个函数结束后,其对应的栈就会被回收,此时方法体中定义的变量就不存在了,因此return 时不是直接返回变量的值,而复制一份,然后返回!!   
发表于 2015-08-15 21:30:53 回复(0)
看代码,你们猜打印结果是什么?
public class Test{
 public static int a(){
     int a = 0;
     try{
         a = 1;
         return a;
     } catch (Exception e) {
     } finally{
         a = 2;
     }
     return 3;
 }
 
 public static void main(String[] args){
     System.out.println(a());
 }
}
打印结果是1
所以答案是C.在return后执行
编辑于 2015-08-24 00:05:20 回复(5)
其实我觉得这道题只要大家理解清楚原理就可以了,没有必要纠结具体答案。
假设我们只考虑return只存在于try块中,而finally块中没有return的情况,那么:
  1. 如果你认为return只要执行了,也即把return返回的值保存在一个局部变量中,从这里开始比较和finally块的执行顺序,那么finally块就是在return之后执行。
  2. 如果你认为return必须将最终结果返回,也即将先前保存在局部变量的值返回,此时程序结束,那么finally块就是在return之前执行。
发表于 2020-06-12 17:33:15 回复(0)
答案选B,如果return出现在try中,在return执行之前会执行finally里的代码。如果finally有return语句的话,finally的return会覆盖try里的return,也就是try的return得不到执行。
发表于 2015-10-06 14:25:40 回复(0)
C finally在try中return后执行。
变量和返回值是分别保存在两个不同的地方,try中return i;时只用i值填充返回值的地址,finally时再次改变i的值,却不会影响返回值。至于finally里能再次return i;也可能是再次修改了返回值那块地址所保存的值。
发表于 2015-08-27 21:34:38 回复(0)
在try的return返回前会执行finally语句,如果finally语句中有return语句,这个返回值会覆盖try的返回值
发表于 2022-03-24 22:49:34 回复(0)
如果try块中存在return,则finally会先执行再返回,如果finally中也有返回值,则会覆盖此返回值
发表于 2018-06-19 08:02:20 回复(0)

除非在try块或者catch块中调用了退出虚拟机的方法(即System.exit(1);),否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行的。不过,一般情况下,不要再finally块中使用return或throw等导致方法终止的语句,因为一旦使用,将会导致try块、catch块中的return、throw语句失效。

 

总结一下这个小问题:

当程序执行try块,catch块时遇到return语句或者throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这两个语句,而是 去寻找该异常处理流程中的finally块,如果没有finally块,程序立即执行return语句或者throw语句,方法终止。如果有 finally块,系统立即开始执行finally块,只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的 return或throw语句,如果finally块里也使用了return或throw等导致方法终止的语句,则finally块已经终止了方法,不用 再跳回去执行try块、catch块里的任何代码了。

综上:尽量避免在finally块里使用return或throw等导致方法终止的语句,否则可能出现一些很奇怪的情况!

 

使用throws抛出异常的思路是:当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道应该如何处理这种类型的异常,也可以使用使用throws声明抛出异常,该异常将交给JVM来处理。

JVM对异常的处理方法:打印异常跟踪栈的信息,并终止程序运行,所以有很多程序遇到异常后自动结束。

发表于 2017-11-30 15:58:35 回复(0)
两条恒古不变的java原则:
1.程序一定是从上往下执行
2.return一定是最后执行的,也就是说return之后的语句执行不到。

编辑于 2022-05-15 16:08:44 回复(0)
根据JVM规范如果try语句块里边有返回值则返回try{}里边的
如果try{}和finally{}都有return,则忽略try{}里边的使用finally{}里边的return;
finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。
try后边不是必须跟catch{},可以跟finally{}
发表于 2019-09-06 12:22:03 回复(0)
个人理解)其实从字面上来看是在return前执行,因为return后不允许执行任何语句的,也就是说在执行return语句的时候监控到有finally语句在,会临时保存返回值,如果finally里面也有返回值的话就覆盖前面的。这也是一种高效的设计,毕竟不是所有try后面都跟着finally语句。如果return后执行就违背了跳出函数后不允许执行语句的原则了,这个会影响到整个结构设计的完整性。
发表于 2018-09-11 22:03:00 回复(0)
finally的内容一定会执行,但是不会影响这种情况的return值,return等它执行完后,立刻返回
发表于 2018-06-15 09:08:37 回复(0)