zoukankan      html  css  js  c++  java
  • Java finally语句到底是在return之前还是之后执行?

    网上有很多人探讨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
    这里因 为在return之前发生了除0异常,所以try中的return不会被执行到,而是接着执行捕获异常的catch 语句和最终的finally语句,此时两者对b的修改都影响了最终的返回值,这时return b;就起到作用了。当然如果你这里将return b改为return 300什么的,最后返回的就是300,这毋庸置疑。

    这里大家可能又有疑问:如果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的处理流程

    [java] view plaincopy
     
     
     
    1. public class TryCatchFinally {   
    2.     @SuppressWarnings("finally")  
    3.     public static final String test() {  
    4.         String t = "";   
    5.         try {  
    6.             t = "try";  
    7.             return t;  
    8.         } catch (Exception e) {  
    9.             // result = "catch";  
    10.             t = "catch";  
    11.             return t;  
    12.         } finally {  
    13.             t = "finally";  
    14.         }  
    15.     }   
    16.     public static void main(String[] args) {  
    17.         System.out.print(TryCatchFinally.test());  
    18.     }   
    19. }  

    首先程序执行try语句块,把变量t赋值为try,由于没有发现异常,接下来执行finally语句块,把变量t赋值为finally,然后return t,则t的值是finally,最后t的值就是finally,程序结果应该显示finally,但是实际结果为try。为什么会这样,我们不妨先看看这段代码编译出来的class对应的字节码,看虚拟机内部是如何执行的。

    我们用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。
    分析:
    在try语句中,在执行return语句时,要返回的结果已经准备好了,就在此时,程序转到finally执行了。
    在转去之前,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,在从中取出返回结果,
    因此,即使finally中对变量x进行了改变,但是不会影响返回结果。
    它应该使用栈保存返回值。

    转自:http://www.cnblogs.com/lanxuezaipiao/p/3440471.html

    http://blog.csdn.net/bubaxiu/article/details/42837931

  • 相关阅读:
    React Native入门教程2 -- 基本组件使用及样式
    React Native入门教程 1 -- 开发环境搭建
    [轻松一下] 大热天的宿舍太热了,来网吧敲代码
    读外部存储的权限READ_EXTERNAL_STORAGE
    【翻译】Ext JS 6.2 早期访问版本发布
    等火车
    HTTP 简介
    建造模式Builder
    DP解LCS问题模板及其优化(模板)
    poj1015 正解--二维DP(完全背包)
  • 原文地址:https://www.cnblogs.com/duanxz/p/3016530.html
Copyright © 2011-2022 走看看