精选文章 finally不是一定会执行

finally不是一定会执行

作者:Ethan-cw 时间: 2020-04-18 11:01:49
Ethan-cw 2020-04-18 11:01:49

在网上看到的帖子,基本上都是说 finally 一定会执行,开始我也一直是这么认为的。但后来我对这块内容做了测试,也看了一些文章,发现 finally不是一定会执行的

 

1,结论

finally不一定会执行,只有与 finally 相应的try代码块得到执行的情况下,finally 才会执行!!

 

2,实例与分析

2,1 finally没有执行的情况

下面的示例代码中,有finally代码块,但结果是 finally 的代码没有被执行,内容没有打印出来。

    public static int test() {
        int i = 1;
        System.out.println("test");
        // 在这里会抛出异常
        i = i / 0;

        try {
            // 若把i = i / 0语句放在try中,finally就会执行
            // i = i / 0;
            System.out.println("try code");
        } finally {
            System.out.println("finally code");
        }
    }

就像上面的这种情况,在 try 代码块前已经出现了异常。在此后的代码都不会执行,finally 自然也不会执行。开始的结论说了,finally 不一定会执行,只有与 finally 相应的try代码块得到执行的情况下,finally 才会执行!!代码中的异常是在 try 代码前出现的,会导致 try 代码块得不到执行,最终 finally 也得不到执行。

 

2,2 finally的执行原理

finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把
程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。

实际上,JVM会把 finally 语句块作为 subroutine(可简单为翻译为子程序)直接插入到 try 语句块或者 catch 语句块的控制
转移语句之前
。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch
语句块会保留其返回值到本地变量表(Local Variable Table)中。等 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)

前面曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。  

 

上面的说明,对应实现情况,就是如果 finally 语句中没有 return 语句覆盖返回值,那么原来的返回值可能因为 finally 里的修改而改变,也可能不变。

finally 没有改变返回值的情况

    public static int test55() {
        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;
            System.out.println("finally, b =" + b);
        }
        return 2000;
    }

这里的结果是100,没有受到 finally 代码块的影响。并不是没有执行 finally 的代码,可以打断点一步步执行。发现开始执行了return b += 80这一行代码,此时 b = 100,在 finally中,b 也确实是150了。但执行完System.out.println("finally, b =" + b)后又跳到了return b += 80这行代码,所以结果还是100。相当于return b += 80执行了两次。

由此也说明,在 finally 代码块前有 return 且 finally 会执行的情况下

第一步:return先执行了一次,然后将其返回值保留到本地变量表(Local Variable Table)中;

第二步:执行 finally代码块的内容;

第三步:执行完 finally代码块后,再次来到最终的return,将其返回给该方法的调用者(invoker)。

finally 改变了返回值

    public static Map getMap() {
        Map map = new HashMap();
        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。执行了 finally 代码块,map确实也是为 null 了。为什么呢?还是按照上面说的三步来分析。

第一步,第一次执行 return map时,此时 map 中的 value 是 TRY。在JVM中,将返回值 map 保留到本地变量表中。

第二步,然后执行了 finally代码块,先是将修改了 map 的内容,将键为 KEY 值为 TRY 修改成了键为 KEY 值为 FINALLY,又将 map 的引用设置为 null。要说明的一点是,其实创建的Map对象还是内存中存在(位于堆中),虽然在 finally 最后中将 map 的引用设置为 null了,但并没有影响到堆中Map对象的内容。此时堆中Map对象的 value 还是 FINALLY。

第三步,finally 代码块执行结束后,再来执行return map,此时会取出之前保存的 map,所以这里的 map 不是null,就是指向了堆中Map对象的内容。还是有一点要注意的是,这里是要返回一个Map对象,有点特殊,而不是返回一个String类型的变量,若只是一个String,那是应该返回 TRY,这里是根据 KEY 来得到它对应的值,而在堆中的Map对象中根据键为 KEY 得到的值就是 FINALLY。

有异常的情况下

    public static int test44() {
        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;
    }

这里返回是201,这个很好理解,在 return b += 80前的一行 b = b / 0报错了,那 return b += 80这一行就不会执行,而是直接进入 catch 代码块执行,再进入 finally 块执行,在 finally 中的b = 85,finally 执行完后,就运行到最后一行返回204。若最后一行是返回的 b,那此时就是85。这里之所以好分析好理解,是因为 return在最后,顺序上很自然,而上面的两个例子的 return 在前面,最终还是跳到去。

 

总结

若 try 语句没有得到执行(比如在 try 前抛出了异常或返回),或在 try 中JVM已经终止(如调用了System.out(0)),那 finally 则不会执行。

finally 语句块是在 try 或者 catch 或finally 或其它地方(如最后一行有return)中的 return 语句之前执行的。下面分情况讨论有catch与没有catch的情况:
    1,有 catch 时,首先当有异常时才会执行 catch;
          a,若在 catch 中抛出异常,那会先执行 finally;
          b,若 catch 中有 return 则最终结果是 catch 中的 return,
          c,但若同时 finally 中也有 return 的话,不管有没有异常,最终结果都是 finally 中的 return。若没有异常则不会执行到                             catch。
    2,没有 catch 时,即只有 try 和 finally。不管有没有异常自然也不执行到 catch,因为就没有 catch 代码。
          a,若有异常时,
                若异常出现在 try 代码前(没有被捕获),则异常处后不再执行,finally 也不会执行
                      (即只有与 finally 相应的 try 语句块得到执行的情况下,finally 语句块才会执行);
                若异常出现在 try 中,则会执行 finally;
          b,若没有异常,
                 当 finally 中有 return 时,则最终结果是 finally 的 return;
                 当 finally 中没有 return 时,则以其它的 return 为最终结果。

 

参考内容

1,关于Java中finally语句块的尝试辨析https://www.ibm.com/developerworks/cn/java/j-lo-finally/index.html

2,Java finally语句到底是在return之前还是之后执行?https://zhuanlan.zhihu.com/p/85377954

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:自监督媲美全监督,港中文、商汤场景去遮挡方法入选 CVPR 2020 Oral

下一篇:keepalived 简介

您可能感兴趣

  • kubespray部署k8s version 1.0

    一、部署原理 基于vagrant和virtualbox,通过kubespray项目,控制ansible,部署高可用的k8s集群。 二、部署环境 1.部署拓扑 2.部署软硬件 硬件: 物理机 CPU:Double CPU,14 core,2 threads per core,CPU Intel(R) Xeon(R) Gold 5120 CPU @ 2.20GHz 内存:64G 软件: Cent...

  • .NET Core:通过Web API进行微服务交互

    目录 介绍 命名协议 MicroCommerce应用程序结构 MicroCommerce应用开发 1.接口项目,微服务接口和模型类 2. ProductCatalog项目 3. ShoppingCart项目 4. ActivityLogger项目 5. WebUI项目,用户界面 6.关于通用功能的几句话 应用测试 结论 缺点 下载源代码26.3 KB 介绍 几乎所有在.NET Core中使用...

  • MemCache详细解读

    本节内容主要关于mencache的工作原理,memcache的应用场景及其应用实例配置,memcache安装部署。 一、Memcached工作原理 1.服务端缓存实现 (1)memcached特性 分布式: 实例和缓存在逻辑上是分离的 普通缓存 Memcached缓存 特 性 缓存与特定的应用实例绑定,每个应用实例只能访问特定的缓存 实例独立于各个应用服务器实例运行,每应用实例可以访问任意缓...

  • 多线程基础例题

    通常使用锁方法(Synchronized修饰方法)、锁代码块(Synchronized块)、信号量(Semaphore)、Lock锁 1、要求线程a执行完才开始线程b, 线程b执行完才开始主线程 思路:由题意可知会有两条副线程a和b,编写好a,b的内容后,在主线程中启动两个线程。 关键点在于,一旦开启线程,线程的执行完全是由各自抢占cpu的能力而定,是人为不可控的,为了实现题目中的要求,我们...

  • Kotlin学习之协程

    协程是什么? 一个线程框架,其实就是一套由Kotlin官方提供的线程API。就像 Java 中的 Executor 和 Android 中的 AsyncTask,Kotlin 中的协程也有对 Thread API 的封装,让我们可以在写代码时,不用关注多线程就能够很方便地写出并发操作。 协程好在哪里? 方便。可以在同一个代码块里进行多次的线程切换。 协程最常用的功能是并发,而并发的典型场景就...

  • JetPack WorkManager

    1.概览 官方文档:WorkManager 谷歌实验室:官方教程 官方案例:android-workmanager WorkManger介绍视频:中文官方介绍视频 谷歌工程师博客:https://medium.com/androiddevelopers/workmanager-basics-beba51e94048 Android JetPack实例学习:https://www.jiansh...

  • 不停机还能替换代码?6年的 Java程序员表示不可思议

    相信很多人都有这样一种感受,自己写的代码在开发、测试环境跑的稳得一笔,可一到线上就抽风,不是缺这个就是少那个反正就是一顿报错,而线上调试代码又很麻烦,让人头疼得很。不过, 阿里巴巴出了一款名叫Arthas的工具,可以在线分析诊断Java代码,让人眼前一亮。 ❞ Arthas 是什么? Arthas(阿尔萨斯) 是阿里开源的一个Java在线分析诊断工具。 Arthas 能解决啥问题? 在日常开...

  • 源码包管理 任务计划——at cron

    一、文件管理 1、源码包管理 (1)概述 Source Code 源代码经过GCC、C++编译环境编译才能运行 可以设定个人设置,开关功能 (2)源码包管理 获取源码包——官网下载到真机,然后 rz 命令上传到虚拟机 ——在网上找到软件包的下载链接,直接 “ wget 链接 ” 就可以下载了 (3)示例——部署 Tengine 下载源码包的命令 [root@localhost ~]# wge...

华为云40多款云服务产品0元试用活动

免费套餐,马上领取!
CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。