网上有很多人探讨Java中异常捕获机制try...catch...finally块中的finally语句是不是一定会被执行?很多人都说不是,当然他们的回答是正确的,经过我试验,至少有两种情况下finally语句是不会被执行的:
(1)try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。
(2)在try块中有System.exit(0);这样的语句,System.exit(0);是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。
当然还有很多人探讨Finally语句的执行与return的关系,颇为让人迷惑,不知道finally语句是在try的return之前执行还是之后执行?我也是一头雾水,我觉得他们的说法都不正确,我觉得应该是:finally语句是在try的return语句执行之后,return返回之前执行。这样的说法有点矛盾,也许是我表述不太清楚,下面我给出自己试验的一些结果和示例进行佐证,有什么问题欢迎大家提出来。
1. finally语句在return语句执行之后return返回之前执行的。
package com.dfs.util; public class FinallyTest1 { public static void main(String[] args) { System.out.println(test1()); } /** * finally块在“return语句执行之后,return返回之前”执行 */ public static int test1() { int b = 20; try { System.out.println("try block"); return b += 80; } catch (Exception e) { System.out.println("catch block"); } finally { System.out.println("finally block"); if (b > 25) { System.out.println("b>25, b = " + b); } } return b; } }
运行结果是:
try block finally block b>25, b = 100 100
说明return语句已经执行了再去执行finally语句,不过并没有直接返回,而是等finally语句执行完了再返回结果。
如果觉得这个例子还不足以说明这个情况的话,下面再加个例子加强证明结论:
package com.dfs.util; public class FinallyTest2 { public static void main(String[] args) { System.out.println(test2()); } /** * finally块在“return语句执行之后,return返回之前”执行 * @return */ public static String test2() { try { System.out.println("try block"); return test22(); } finally { System.out.println("finally block"); } } public static String test22() { System.out.println("return statement"); return "after return"; } }
运行结果为:
try block return statement finally block after return
说明try中的return语句先执行了但并没有立即返回,等到finally执行结束后再返回
这里大家可能会想:如果finally里也有return语句,那么是不是就直接返回了,try中的return就不能返回了?看下面。
2. finally块中的return语句会覆盖try块中的return返回。
package com.dfs.util; public class FinallyTest3 { public static void main(String[] args) { System.out.println(test3()); } /** * finally块在“return语句执行之后,return返回之前”执行 * @return */ public static int test3() { int b = 20; try { System.out.println("try block"); return b += 80; } catch (Exception e) { System.out.println("catch block"); } finally { System.out.println("finally block"); if (b > 25) { System.out.println("b>25, b = " + b); } return 200; //这里和下面的return只能有一个 } //return b; //这里和finally里面的return只能有一个,否则会报错“不可到达的语句” } }
运行结果是:
try block finally block b>25, b = 100 200
这说明finally里的return直接返回了,就不管try中是否还有返回语句,这里还有个小细节需要注意,finally里加上return过后,finally外面的return b就变成不可到达语句了,也就是永远不能被执行到,所以需要注释掉否则编译器报错。
这里大家可能又想:如果finally里没有return语句,但修改了b的值,那么try中return返回的是修改后的值还是原值?看下面。
3. 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。
测试用例1:
package com.dfs.util; public class FinallyTest4 { public static void main(String[] args) { System.out.println(test4()); } public static int test4() { int b = 20; try { System.out.println("try block"); return b += 80; } catch (Exception e) { System.out.println("catch block"); } finally { System.out.println("finally block"); if (b > 25) { System.out.println("b>25, b = " + b); } b = 150; } return b; } }
运行结果是:
try block finally block b>25, b = 100 100
测试用例2:
package com.dfs.util; public class FinallyTest55 { public static void main(String[] args) { Integer b = 20; System.out.println(test6(b)); } public static Integer test6(Integer b) { b = b +80; try { System.out.println("try block"); return b; } finally { System.out.println("finally block"); if (b > 25) { System.out.println("b>25, b = " + b); } b = 150; System.out.println("b:=" + b); } } }
运行结果:
try block finally block b>25, b = 100 b:=150 100
测试用例3:
package com.dfs.util; import java.util.*; public class FinallyTest6 { public static void main(String[] args) { System.out.println(getMap().get("KEY").toString()); } public static Map<String, String> getMap() { Map<String, String> map = new HashMap<String, String>(); map.put("KEY", "INIT"); try { map.put("KEY", "TRY"); return map; } catch (Exception e) { map.put("KEY", "CATCH"); } finally { map.put("KEY", "FINALLY"); map = null; } return map; } }
运行结果是:
FINALLY
为什么测试用例1中finally里的b = 150;并没有起到作用而测试用例2中finally的map.put("KEY", "FINALLY");起了作用而map = null;却没起作用呢?这就是Java到底是传值还是传址的问题了,具体请看精选30道Java笔试题解答,里面有详细的解答,简单来说就是:Java中只有传值没有传址,这也是为什么map = null这句不起作用。这同时也说明了返回语句是try中的return语句而不是 finally外面的return b;这句,不相信的话可以试下,将return b;改为return 294,对原来的结果没有一点影响。
这里大家可能又要想:是不是每次返回的一定是try中的return语句呢?那么finally外的return b不是一点作用没吗?请看下面。
4. try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况。
public class FinallyTest4 { public static void main(String[] args) { System.out.println(test4()); } public static int test4() { int b = 20; try { System.out.println("try block"); b = b / 0; return b += 80; } catch (Exception e) { b += 15; System.out.println("catch block"); } finally { System.out.println("finally block"); if (b > 25) { System.out.println("b>25, b = " + b); } b += 50; } return 204; } }
运行结果是:
try block catch block finally block b>25, b = 35 85
这里大家可能又有疑问:如果catch中有return语句呢?当然只有在异常的情况下才有可能会执行,那么是在finally之前就返回吗?看下面。
5. 当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样。
public class FinallyTest5 { public static void main(String[] args) { System.out.println(test5()); } public static int test5() { int b = 20; try { System.out.println("try block"); b = b /0; return b += 80; } catch (Exception e) { System.out.println("catch block"); return b += 15; } finally { System.out.println("finally block"); if (b > 25) { System.out.println("b>25, b = " + b); } b += 50; } //return b; } }
运行结果如下:
try block catch block finally block b>25, b = 35 35
说明了发生异常后,catch中的return语句先执行,确定了返回值后再去执行finally块,执行完了catch再返回,finally里对b的改变对返回值无影响,原因同前面一样,也就是说情况与try中的return语句执行完全一样。
return返回值疑惑
下面看一个例子(例1),来讲解java里面中try、catch、finally的处理流程
- public class TryCatchFinally {
- @SuppressWarnings("finally")
- public static final String test() {
- String t = "";
- try {
- t = "try";
- return t;
- } catch (Exception e) {
- // result = "catch";
- t = "catch";
- return t;
- } finally {
- t = "finally";
- }
- }
- public static void main(String[] args) {
- System.out.print(TryCatchFinally.test());
- }
- }
我们用javap -verbose TryCatchFinally 来显示目标文件(.class文件)字节码信息
系统运行环境:mac os lion系统 64bit
jdk信息:Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-11M3527) Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)
编译出来的字节码部分信息,我们只看test方法,其他的先忽略掉
public static final java.lang.String test(); Code: Stack=1, Locals=4, Args_size=0 0: ldc #16; //String 2: astore_0 3: ldc #18; //String try 5: astore_0 6: aload_0 7: astore_3 8: ldc #20; //String finally 10: astore_0 11: aload_3 12: areturn 13: astore_1 14: ldc #22; //String catch 16: astore_0 17: aload_0 18: astore_3 19: ldc #20; //String finally 21: astore_0 22: aload_3 23: areturn 24: astore_2 25: ldc #20; //String finally 27: astore_0 28: aload_2 29: athrow Exception table: from to target type 3 8 13 Class java/lang/Exception 3 8 24 any 13 19 24 any LineNumberTable: line 5: 0 line 8: 3 line 9: 6 line 15: 8 line 9: 11 line 10: 13 line 12: 14 line 13: 17 line 15: 19 line 13: 22 line 14: 24 line 15: 25 line 16: 28 LocalVariableTable: Start Length Slot Name Signature 3 27 0 t Ljava/lang/String; 14 10 1 e Ljava/lang/Exception; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 13 locals = [ class java/lang/String ] stack = [ class java/lang/Exception ] frame_type = 74 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ]
首先看LocalVariableTable信息,这里面定义了两个变量 一个是t String类型,一个是e Exception 类型
接下来看Code部分
第[0-2]行,给第0个变量赋值“”,也就是String t="";
第[3-6]行,也就是执行try语句块 赋值语句 ,也就是 t = "try";
第7行,重点是第7行,把第s对应的值"try"付给第三个变量,但是这里面第三个变量并没有定义,这个比较奇怪
第[8-10] 行,对第0个变量进行赋值操作,也就是t="finally"
第[11-12]行,把第三个变量对应的值返回
通过字节码,我们发现,在try语句的return块中,return 返回的引用变量(t 是引用类型)并不是try语句外定义的引用变量t,而是系统重新定义了一个局部引用t’,这个引用指向了引用t对应的值,也就是try ,即使在finally语句中把引用t指向了值finally,因为return的返回引用已经不是t ,所以引用t的对应的值和try语句中的返回值无关了。
下面在看一个例子:(例2)
1 public class TryCatchFinally { 2 3 @SuppressWarnings("finally") 4 public static final String test() { 5 String t = ""; 6 7 try { 8 t = "try"; 9 return t; 10 } catch (Exception e) { 11 // result = "catch"; 12 t = "catch"; 13 return t; 14 } finally { 15 t = "finally"; 16 return t; 17 } 18 } 19 20 public static void main(String[] args) { 21 System.out.print(TryCatchFinally.test()); 22 } 23 24 }
这里稍微修改了 第一段代码,只是在finally语句块里面加入了 一个 return t 的表达式。
按照第一段代码的解释,先进行try{}语句,然后在return之前把当前的t的值try保存到一个变量t',然后执行finally语句块,修改了变量t的值,在返回变量t。
这里面有两个return语句,但是程序到底返回的是try 还是 finally。接下来我们还是看字节码信息
public static final java.lang.String test(); Code: Stack=1, Locals=2, Args_size=0 0: ldc #16; //String 2: astore_0 3: ldc #18; //String try 5: astore_0 6: goto 17 9: astore_1 10: ldc #20; //String catch 12: astore_0 13: goto 17 16: pop 17: ldc #22; //String finally 19: astore_0 20: aload_0 21: areturn Exception table: from to target type 3 9 9 Class java/lang/Exception 3 16 16 any LineNumberTable: line 5: 0 line 8: 3 line 9: 6 line 10: 9 line 12: 10 line 13: 13 line 14: 16 line 15: 17 line 16: 20 LocalVariableTable: Start Length Slot Name Signature 3 19 0 t Ljava/lang/String; 10 6 1 e Ljava/lang/Exception; StackMapTable: number_of_entries = 3 frame_type = 255 /* full_frame */ offset_delta = 9 locals = [ class java/lang/String ] stack = [ class java/lang/Exception ] frame_type = 70 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] frame_type = 0 /* same */
这段代码翻译出来的字节码和第一段代码完全不同,还是继续看code属性
第[0-2]行、[3-5]行第一段代码逻辑类似,就是初始化t,把try中的t进行赋值try
第6行,这里面跳转到第17行,[17-19]就是执行finally里面的赋值语句,把变量t赋值为finally,然后返回t对应的值
我们发现try语句中的return语句给忽略。可能jvm认为一个方法里面有两个return语句并没有太大的意义,所以try中的return语句给忽略了,直接起作用的是finally中的return语句,所以这次返回的是finally。
接下来在看看复杂一点的例子:(例3)
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try"; Integer.parseInt(null); return t; } catch (Exception e) { t = "catch"; return t; } finally { t = "finally"; // System.out.println(t); // return t; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
这里面try语句里面会抛出 java.lang.NumberFormatException,所以程序会先执行catch语句中的逻辑,t赋值为catch,在执行return之前,会把返回值保存到一个临时变量里面t ',执行finally的逻辑,t赋值为finally,但是返回值和t',所以变量t的值和返回值已经没有关系了,返回的是catch
例4:
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try"; Integer.parseInt(null); return t; } catch (Exception e) { t = "catch"; return t; } finally { t = "finally"; return t; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
这个和例2有点类似,由于try语句里面抛出异常,程序转入catch语句块,catch语句在执行return语句之前执行finally,而finally语句有return,则直接执行finally的语句值,返回finally
例5:
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try"; Integer.parseInt(null); return t; } catch (Exception e) { t = "catch"; Integer.parseInt(null); return t; } finally { t = "finally"; //return t; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
这个例子在catch语句块添加了Integer.parser(null)语句,强制抛出了一个异常。然后finally语句块里面没有return语句。继续分析一下,由于try语句抛出异常,程序进入catch语句块,catch语句块又抛出一个异常,说明catch语句要退出,则执行finally语句块,对t进行赋值。然后catch语句块里面抛出异常。结果是抛出java.lang.NumberFormatException异常
例子6:
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try"; Integer.parseInt(null); return t; } catch (Exception e) { t = "catch"; Integer.parseInt(null); return t; } finally { t = "finally"; return t; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
这个例子和上面例子中唯一不同的是,这个例子里面finally 语句里面有return语句块。try catch中运行的逻辑和上面例子一样,当catch语句块里面抛出异常之后,进入finally语句快,然后返回t。则程序忽略catch语句块里面抛出的异常信息,直接返回t对应的值 也就是finally。方法不会抛出异常
例子7:
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try"; Integer.parseInt(null); return t; } catch (NullPointerException e) { t = "catch"; return t; } finally { t = "finally"; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
这个例子里面catch语句里面catch的是NPE异常,而不是java.lang.NumberFormatException异常,所以不会进入catch语句块,直接进入finally语句块,finally对s赋值之后,由try语句抛出java.lang.NumberFormatException异常。
例子8
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try"; Integer.parseInt(null); return t; } catch (NullPointerException e) { t = "catch"; return t; } finally { t = "finally"; return t; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
和上面的例子中try catch的逻辑相同,try语句执行完成执行finally语句,finally赋值s 并且返回s ,最后程序结果返回finally
例子9:
public class TryCatchFinally { @SuppressWarnings("finally") public static final String test() { String t = ""; try { t = "try";return t; } catch (Exception e) { t = "catch"; return t; } finally { t = "finally"; String.valueOf(null); return t; } } public static void main(String[] args) { System.out.print(TryCatchFinally.test()); } }
这个例子中,对finally语句中添加了String.valueOf(null), 强制抛出NPE异常。首先程序执行try语句,在返回执行,执行finally语句块,finally语句抛出NPE异常,整个结果返回NPE异常。
对以上所有的例子进行总结
1 try、catch、finally语句中,在如果try语句有return语句,则返回的之后当前try中变量此时对应的值,此后对变量做任何的修改,都不影响try中return的返回值
2 如果finally块中有return 语句,则返回try或catch中的返回语句忽略。
3 如果finally块中抛出异常,则整个try、catch、finally块中抛出异常
所以使用try、catch、finally语句块中需要注意的是
1 尽量在try或者catch中使用return语句。通过finally块中达到对try或者catch返回值修改是不可行的。
2 finally块中避免使用return语句,因为finally块中如果使用return语句,会显示的消化掉try、catch块中的异常信息,屏蔽了错误的发生
3 finally块中避免再次抛出异常,否则整个包含try语句块的方法回抛出异常,并且会消化掉try、catch块中的异常
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
举例:
情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
下面是个测试程序 public class FinallyTest { public static void main(String[] args) { System.out.println(new FinallyTest().test());; } static int test() { int x = 1; try { x++; return x; } finally { ++x; } } } 结果是2。
分析:
转自:http://www.cnblogs.com/lanxuezaipiao/p/3440471.html
http://blog.csdn.net/bubaxiu/article/details/42837931