zoukankan      html  css  js  c++  java
  • Java 异常处理try、catch、finally和return执行顺序

    问题背景

    finally语句一定执行吗

       网上有很多人探讨Java中异常捕获机制 try...catch...finally 块中的finally语句是不是一定会被执行?很多人都说不是,当然他们的回答是正确的,至少有两种情况下finally语句是不会被执行的:

       (1)try语句没有被执行到就不会执行finally。如在try语句之前程序结束执行,那么finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是相应的try语句一定被执行到

       (2)在try块中执行了System.exit(0)。System.exit(0)的作用是终止Java虚拟机,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。

    finally语句与return的执行顺序

       还有很多人探讨finally语句的执行与return的关系,让人颇为迷惑,不知道finally语句是在try的return之前执行还是之后执行?我觉得应该是:finally语句是在try的return语句执行之后,return返回之前执行。这样的说法有点矛盾,也许是我表述不太清楚,下面我给出自己试验的一些结果和示例进行佐证,有什么问题欢迎大家提出来。

      记return后的语句为y=f(),则finally语句在f()执行之后、y返回之前执行。

    问题分析

    1. finally语句在f()执行之后、y返回之前执行

    public class FinallyTest1 {
    
        public static void main(String[] args) {
              System.out.println(test1());
        }
    
        public static String test1() {
            try {
                System.out.println("try block");
                 // ①计算 f() 结果,记作 y,② 执行 finally,③ 返回 y
                return test2();
            } finally {
                System.out.println("finally block");
            }
        }
    
        public static String test2() {
            System.out.println("return statement");
            return "after return";
        }
    

      运行结果为:

    try block
    return statement
    finally block
    after return

      说明try中的f() = test2() 先执行了,得到结果y,并且待finally块执行结束后再返回y。这里大家可能会想:如果finally里也有return语句,那么是不是就直接返回了,try中的return就不能返回了?看下面。

    2. finally块中的return语句覆盖try块中的y

    public class FinallyTest2 {
       public static void main(String[] args) {
          System.out.println(test2());
       }
        public static int test2() {
            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 b;
        }
    

    运行结果是:

    try block
    finally block
    b>25, b = 100
    200

    这说明finally里的return直接返回了,就不管try中是否还有返回语句,这也是不建议在finally中使用return的原因。这里还有个小细节需要注意,finally里加上return过后,try...catch...finally 外面的return b就变成不可到达语句了,也就是永远不能被执行到,所以需要注释掉;否则,编译失败。

    那么直接从finally中返回了,这也是不建议在finally中return的原因。

    这里大家可能又想:如果finally里没有return语句,但修改了b的值,那么try中return返回的是修改后的值还是原值?看下面。

    3. finally语句块不改变栈中的值,可改变堆中的值

    本节讨论如下场景:finally中没有return语句,但是改变了要返回的值。返回值y的类型如果是基本类型,则在finally块里被修改也不影响y,如果是引用类型则可以。如果y是文本字符串,也不受影响。

    返回基本类型变量时是值,返回引用类型时是指向某个对象的地址;而且基本类型是被分配在栈中的,对象是被分配在堆中的,只要有引用指向这个对象,系统就不会回收此对象,所以可以在后面的finally块中改变引用指向的对象的内容,却无法改变try语句中return要返回的值,因为这个值已经与变量y无关了。

    测试用例1:

    public class FinallyTest3 {
    public static void main(String[] args) {
    System.out.println(test3());
    }
        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);
                }
                b = 150; //基本类型的返回值y,不可被修改
                // return 3200; //可以被“具体值”覆盖
            }
            return 2000;
        }
    
    }
    

    运行结果是:

    try block
    finally block
    b>25, b = 100
    100

    执行结果说明没有修改y。这也验证了finally语句块不会影响到栈中的值,即在执行finally之前,栈中的临时值已经确定为100了,执行finally语句将b的值变为150,对结果没有任何影响,执行完finally后的输出结果仍为100。

    测试用例2:

    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");// 修改y成功
               map = null;
               System.out.println("map是:" + map);
           }
           System.out.println("---- 走不到此处 --------");//  ①
           return map;
       }
    

    运行结果是:

    map是:null
    FINALLY
    

    为什么案例 test3()中finally里的b = 150并没有起到作用而案例 getMap()中finally的map.put("KEY", "FINALLY")起了作用,但是map = null却没起作用呢?这就是Java到底是传值还是传址的问题了,简单来说就是:Java中只有传值没有传址,这也是为什么map = null这句不起作用。 ①处的代码未执行说明finally外面的return 这句未执行。

    测试用例3:

        public static String testStr() {
            String b = "init";
            try {
                System.out.println("try block");
                return b;
            } catch (Exception e) {
                System.out.println("catch block");
            } finally {
                b = b + "--";
                System.out.println("finally block, b = " + b);
            }
            return b;
        }
    

    执行结果:

    try block
    finally block, b = init--
    init
    

    说明如果y是文本字符串,在finally语句块中改变它,也影响返回值。

    这里大家可能又要想:是不是每次返回的一定是try中的return语句呢?那么finally外的return 语句一点作用没吗?请看下面。

    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, b = " + b);
            } finally {
                System.out.println("finally block");
                if (b > 25) {
                    System.out.println("b>25, b = " + b);
                }
                b += 50;
            }
            System.out.println("---- 走此处 --------" + b);
            return 320 + b; // ②
        }
    

    运行结果是:

    try block
    catch block, b = 35
    finally block
    b>25, b = 35
    ---- 走此处 --------85
    405

    这里因 为在return之前发生了除0异常,所以①不会被执行,而是接着执行捕获异常的catch 语句和最终的finally语句。此时两者对b的修改都影响了最终的返回值,从②返回的结果可以看出来。大家可能又有疑问:如果catch中有return语句呢?当然只有在异常的情况下才有可能会执行,那么是在finally之前就返回吗?看下面。

    5. catch和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语句先执行,计算完返回值y后将其保存起来,再去执行finally块;执行完finally块就把先去保存的y返回,finally里修改b对返回值y无影响,原因同前面的 test3() 。至此,也就推理出结论:catch中的return语句和try中的执行顺序完全一样。

    小结

    • finally块的语句在try或catch中的return语句执行之后返回之前执行。
    • finally语句块不改变栈中的值y,可改变堆中的值。
      y的类型如果是基本类型,其临时存放在栈中,则在finally块里被修改也不影响y,如果是存放在堆中的引用类型则可以。finally块里改变文本字符串也不影响y。
    • finally里的return语句覆盖try或catch中的return语句直接返回。

    下面看一个甲骨文的面试题:

    public class test {
        public static void main(String[] args) {
            try {
                aMethod();
            } catch (Exception e) {
                System.out.println("exception");// ①
            }
            System.out.println("finish");// ②
        }
    
        public static void aMethod() throws Exception {
            try {
                throw new Exception();
            } finally {
                System.out.println("finally"); // ③
            }
        }
    }
    

    面试题分析:首先,进入上述try代码块后,不管try是否抛异常,finally块一定会执行,因此抛异常之前会执行 ③。然后,抛出的异常被调用者main方法捕获后执行①。最后,执行②。故执行结果如下:

    finally
    exception
    finish
    

    Reference


      读后有收获,小礼物走一走,请作者喝咖啡。

    赞赏支持

  • 相关阅读:
    leetcode 29-> Divide Two Integers without using multiplication, division and mod operator
    ros topic 发布一次可能会接收不到数据
    python中的print()、str()和repr()的区别
    python 部分函数
    uiautomatorviewer错误 unable toconnect to adb
    pyqt 不规则形状窗口显示
    appium 计算器demo
    Spring 3.0 注解注入详解
    Spring Autowire自动装配
    restful 学习地址
  • 原文地址:https://www.cnblogs.com/east7/p/14533249.html
Copyright © 2011-2022 走看看