前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >由一道 Java finally 执行顺序的题引发的思考

由一道 Java finally 执行顺序的题引发的思考

作者头像
一份执着✘
发布2018-06-04 17:17:27
5350
发布2018-06-04 17:17:27
举报
文章被收录于专栏:赵俊的Java专栏赵俊的Java专栏

前言

本文讲解过于繁琐,已重新修改至 关于 Java finally 执行顺序 -- 修改版

原题

首先来看看这道题:

代码语言:javascript
复制
public class test1 {
    static String s = "A";

    public static void main(String[] args) {
        System.out.println(test());
        System.out.println(s);
    }

    private static String test() {
        try {
            System.out.println("A");
            return s = "A";
        } finally {
            System.out.println("B");
            s = "B";
        }
    }
}

执行结果是 ABAB ,刚看到这个结果,我是比较诧异的,利用 IDEA 的单步调试也没弄明白,来看看我当时的思路:

  1. 首先输出 try 中的输出语句,输出 "A"
  2. 由于 try 下还有的 finally 语句,所以执行 finally 中的输出语句,输出 "B"
  3. "B" 赋值给变量 s
  4. 回到 try 中的 return 语句,再次将 "A" 赋值给 s,将 "A" 最为返回值,返回 main 方法中。(此时 s = "A")
  5. 回到 main 方法中的第一行输出语句,输出 "A",然后执行 main 方法中的第二条输出语句,输出 "A"

所以,结果不应该是 “ABAA” 么,因为 return 中已经将 “A” 赋值给 s 了,那么 s 的值不应该是 “A” 么,这时返回值就是 “A”,再怎么第二条输出语句也不能是 “B” 呀,可看着开发工具输出的结果,我也很无奈,我当时是这个表情。。。

但我相信开发工具是不会骗我的,一定是我自己的理解有问题,然后自己又写了几个类似的例子来测试,最后得出的结论是: finally 语句块在 return 语句之后,return 返回之前执行,可能这句话不是那么容易理解,来看下面的例子吧,看完我相信你应该能明白我这句话是什么意思了。

例一

证明:finally 语句块在 return 语句之后,return 返回之前执行。

代码语言:javascript
复制
public class FinallyTest1 {

    public static void main(String[] args) {
        System.out.println(test1());
    }

    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;
    }

}

运行结果:

代码语言:javascript
复制
try block
finally block
b>25, b = 100
100

可以根据运行结果得知,在 finally 语句块执行之前,return b += 80; 中的 b + =80; 这部分已经运行,但是没有返回,然后等 finally 执行完以后才返回结果的。

例二

如果觉得这个 例 1 还不足以说明这个情况的话,下面再加个例子加强证明结论:

代码语言:javascript
复制
public class FinallyTest1 {

    public static void main(String[] args) {
        
        System.out.println(test11());
    }
    
    public static String test11() {
        try {
           System.out.println("try block");

           return test12();
      } finally {
           System.out.println("finally block");
       }
  }

  public static String test12() {
       System.out.println("return statement");

       return "after return";
   }
    
}

运行结果:

代码语言:javascript
复制
try block
return statement
finally block
after return

这下总明白了吧,执行 finally 之前将 return 后的 test12() 方法先执行了,然后再执行的 finally 语句。

拓展问题一

前面两个例子证明了原题得出的结论,但是这个结论好像还不能足矣证明原题中的结果为什么是"ABAB",还有一个新问题:

如果,finally 中对 return 返回的变量进行了修改,那么 return 返回的是修改前的值还是修改后的值?

我们在 例 1finally 语句块中加上一句 b = 10;,那么结果又会是什么呢?

代码语言:javascript
复制
public class FinallyTest2 {

    public static void main(String[] args) {

        System.out.println(test1());
    }

    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);
            }
            b = 10;
        }

        return b;
    }

}

运行结果:

代码语言:javascript
复制
try block
finally block
b>25, b = 100
100

可以看到 return 的值并没有被 finally 中的语句改变,其实如果这样得出结论:finally 中对 return 返回的变量进行了修改,并不会影响 try 中 return 的值,是不负责任的,因为我们只考虑了基本数据类型,如果是引用数据类型,还会是这种结果么?

扩展问题二

拓展问题 1 的又一个问题,对于引用数据类型的情况:

代码语言:javascript
复制
public class FinallyTest3 {

    public static void main(String[] args) {

        System.out.println(test1().getName());
    }

    public static Student test1() {
        Student stu = new Student();

        try {
            stu.setName("Try");
            return stu;
        } catch (Exception e) {
            stu.setName("Catch");
        } finally {
            stu.setName("Finally");
        }

        return stu;
    }

}

class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

运行结果:

代码语言:javascript
复制
Finally

可以看到,对于基本数据类型和引用数据类型,结论是不同的,再次总结下结论吧:

  • 对于基本数据类型,finally 语句块中对 try 语句块的 return 的变量进行了修改,return 的则是修改前的值。
  • 对于引用数据类型,finally 语句块中对 try 语句块的 return 的变量进行了修改,return 的则是修改后的值。

看到这里,不知道你有没有想到这个结论与 Java 的方法调用时的所谓的 值传递引用传递 的结论有些类似。

既然得出了这个结论,那么这个结论是必然的情况么?一定正确么?别急,来看下一个例子。

拓展问题三

对于 拓展问题 2 我再做一个增强版的,其实就是在第 17 行之下添加一条语句 stu = null

代码语言:javascript
复制
public class FinallyTest4 {

    public static void main(String[] args) {

        System.out.println(test1().getName());
    }

    public static Student test1() {
        Student stu = new Student();

        try {
            stu.setName("Try");
            return stu;
        } catch (Exception e) {
            stu.setName("Catch");
        } finally {
            stu.setName("Finally");
            stu = null;
        }

        return stu;
    }

}

class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

运行结果:

代码语言:javascript
复制
Finally

这时,你可能都有骂人的冲动了,What ?咋回事,咋什么结论都不对呢,不是说好的引用数据类型会改变 return 的值么,这里都将 stu 修改为 null 了,怎么还能 return"Finally" 呢?

别激动,别激动,不卖关子了,我直接说了:

先说基本数据类型,由于基本数据类型是 值传递 ,所以在 try 里的 return 执行的时候,就先将变量的值隐性的作为最终返回的值。 同样的,对于引用数据类型,只是将该变量所指向的内存地址隐性的作为最终返回的值,所以即使将 stu = null,也无所谓了,因为早在执行 finally 之前,try 里的 return 就已经拿到了 stu 所指向的地址。 这里多说一句,其实 Java 里都是值传递,只不过基本数据类型的值是真正的值,而引用数据类型是地址值而已。(如果你看这句话更晕的话,就先跳过去这句话,反正本篇文章也不是为了解释这个的。)

更深的思考


问:如果 tryfinally 中都有 return 语句,那么到底会返回哪一个呢?

会返回 finally 中的 return


问:如果 try 里有 return 语句,整个 try-catch-finally 块之外也有一个 return 语句,那么哪个会执行,一定是这样么?

try 里没有发生异常的情况下,是 try 里的 return 会执行,但发生了异常,则反之。


问:如果 catch 中有 return 语句呢?当然只有在异常的情况下才有可能会执行,那么是在 finally 之前就返回吗?

当发生异常后,catch 中的 return 执行情况与未发生异常时 tryreturn 的执行情况完全一样。


最终总结

finally 块的语句在 try 或 catch 中的 return 语句执行之后返回之前执行,且 finally 里的修改语句可能影响也可能不影响 try 或 catch 中 return 已经确定的返回值,若 finally 里也有 return 语句则覆盖 try 或 catch 中的 return 语句直接返回。

本文参考

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-12-072,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 原题
  • 例一
  • 例二
  • 拓展问题一
  • 扩展问题二
  • 拓展问题三
  • 更深的思考
  • 最终总结
  • 本文参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档