Java 中的 finally 代码块不执行的情况

本文发布于 2024年07月16日,阅读 38 次,点赞 0 次,归类于 Java

by emanjusaka from https://www.emanjusaka.top/2024/07/java-finally-non-execution 彼岸花开可奈何

本文为原创文章,可能会更新知识点以及修正文中的一些错误,全文转载请保留原文地址,避免未即时修正的错误误导。

一、前言

先抛出一个问题:Java 中的 finally 代码块一定会被执行吗?

这是一个比较常见的面试题,在我们的印象中好像 finally 的代码块是一定会被执行的。但真实的情况是这样的吗?其实答案是否定的,有些情况下它是不被执行的。下面我们来盘点下 finally 代码块不会执行的情况。

二、finally 不执行的情况

1、在执行 try 块之前就直接 return 的情况

 package top.emanjusaka.trycatch;
 ​
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 ​
 /**
  * @Author emanjusaka
  * @Date 2024/7/16 14:08
  * @Version 1.0
  */
 @SpringBootTest
 public class TryCatchTest {
     private int tryReturnBefore() {
         int i = 0;
         if (i == 0) {
             return ++i;
         }
         try {
             System.out.println("执行了 try");
         } catch (Exception e) {
             System.out.println(e);
         } finally {
             System.out.println("执行了 finally");
         }
         return i;
     }
 ​
     @Test
     void testReturn() {
         System.out.println("输出了 i = " + tryReturnBefore());
     }
 }
 ​

执行结果:

image-20240716141942293

在上面的代码中执行的最终结果并没有输出“执行了 finally”,这说明代码并没有执行 finally 的代码块甚至连 try 块也没有执行。因为在 try 块之前代码就已经 return 了,代码到此就会终止下面的 try...catch...finally 都不会执行了。这就是 finally 块不执行的情况之一。现在我们想想如果在 try 块执行之前没有 return 而是出现了 error会怎样呢?还会执行下面的 finally 块吗?

2、在执行 try 块之前出现了错误

  private void tryErrorBefore() {
         int i = 0;
         System.out.println("输出了 i = " + i / 0);
         try {
             System.out.println("执行了 try");
         } catch (Exception e) {
             System.out.println(e);
         } finally {
             System.out.println("执行了 finally");
         }
     }
 ​
     @Test
     void testError() {
         tryErrorBefore();
     }

执行结果:

image-20240716142753878

这个结果就回答了上面的问题,如果在 try 块执行之前出现了 error,finally 代码块依然是不会执行的。这和上面的情况差不多是同样的道理,代码都是在进入 try 块之前就终止了,只不过一个是 return 了一个是出现了 error。那如果代码执行到了try 块中了呢,这时候 finally 代码块一定会执行了吗?答案同样是否定的,下面介绍一种同样特殊的情况。

3、在执行 try 块之中退出 jvm

     @Test
     void testExit() {
         int i = 0;
         try {
             System.out.println("执行了 try");
             System.out.println("输出了 i = " + i);
             System.exit(0);
         } finally {
             System.out.println("执行了 finally");
         }
     }

执行结果:

image-20240716144337421

看到执行结果后,我们发现即使执行了 try 块,但是 finally 代码块同样没有执行。这是因为代码执行到System.exit(0)时,语句会立即终止当前运行的 Java 虚拟机,程序会立即退出,不会继续执行后续的代码,包括 finally 代码块。

上面的三种情况可能是比较极端,但也是可以作为在出现 finally 没有执行的情况下的排查方向的。

三、分析finally 的执行顺序

在上面我们盘点了三种 finally 代码块不会执行的情况。下面我们来研究下 finally 代码块的执行顺序是怎样的?

它是在 try 块之前执行?还是之后执行?如果再加上 catch 块它们的执行顺序又是什么呢?

     private int finallyOrder() {
         int i = 0;
         try {
             System.out.println("执行了 try");
             return ++i;
         } finally {
             System.out.println("执行了 finally");
         }
     }
 ​
     @Test
     void testOrder() {
         System.out.println("输出了 i = " + finallyOrder());
     }

这段代码的输出的顺序是啥?

 执行了 try
 执行了 finally
 输出了 i = 1

可以看出 finally 代码块是在 try 块方法返回之前执行的。如果 try...catch捕获到异常了,finally 代码块的执行时机是在哪呢?

  private int finallyExceptionOrder() {
         int i = 0;
         try {
             System.out.println("执行了 try");
             return i / 0;
         } catch (Exception e) {
             System.out.println("捕获了异常");
             return i + 1;
         } finally {
             System.out.println("执行了 finally");
         }
     }
 ​
     @Test
     void testOrder() {
         System.out.println("输出了 i = " + finallyExceptionOrder());
     }

执行结果为:

 执行了 try
 捕获了异常
 执行了 finally
 输出了 i = 1

同样从结果可以看出,finally 代码块是在 catch 中方法返回之前执行的。

现在我们想一下这个操作,在 finally 代码块中改变变量 i 的值会对 try 块或者 catch 块中返回值造成影响吗?

四、几种finally 块中的返回值的情况

     private int finallyChangeVariable() {
         int i = 0;
         try {
             System.out.println("执行了 try");
             return i + 1;
         } finally {
             ++i;
             System.out.println("执行了 finally");
         }
     }
 ​
     @Test
     void testOrder() {
         System.out.println("输出了 i = " + finallyChangeVariable());
     }

我们预想中结果应该是:

 执行了 try
 执行了 finally
 输出了 i = 2

但实际真的是这样的吗?答案当然是 no。实际上输出的 i 依然是 1:

 执行了 try
 执行了 finally
 输出了 i = 1

这是什么原因呢?按照上面的正常逻辑首先执行 try 块然后在方法返回之前去执行 finally 块,在 finally 块中不是对 i 进行了自增吗?怎么输出的结果还是 1。

这是因为Java程序会把try或者catch块中的返回值保留,也就是暂时的确认了返回值,然后再去执行finally代码块中的语句。等到finally代码块执行完毕后,如果finally块中没有返回值的话,就把之前保留的返回值返回出去。

如果 return 方法不在 try 或者 catch 块中呢,finally 块对变量的改变会有作用吗?

  1. return 方法在 finally 块中

         private int finallyChangeVariable() {
             int i = 0;
             try {
                 System.out.println("执行了 try");
             } finally {
                 ++i;
                 System.out.println("执行了 finally");
                 return i + 1;
             }
         }
     ​
         @Test
         void testOrder() {
             System.out.println("输出了 i = " + finallyChangeVariable());
         }

  2. return 方法 try...catch...finally 之外

         private int finallyChangeVariable() {
             int i = 0;
             try {
                 System.out.println("执行了 try");
             } finally {
                 ++i;
                 System.out.println("执行了 finally");
             }
             return i + 1;
         }
     ​
         @Test
         void testOrder() {
             System.out.println("输出了 i = " + finallyChangeVariable());
         }

我们发现上面两种情况结果是一样的:

 执行了 try
 执行了 finally
 输出了 i = 2

finally 块对变量的改变都发挥了作用。通过上面我们发现在finally块中进行return操作的话,则方法整体的返回值就是finally块中的return返回值。如果在finally块之后的方法内return,则return的值就是进行完上面的操作后的return值。

在 try 块和 finally 块中都有 return 方法时,最终执行的是 try 块中的 return 方法还是 finally 块中的方法呢?

     private int finallyChangeVariable() {
         int i = 0;
         try {
             System.out.println("执行了 try");
             return i + 1;
         } finally {
             System.out.println("执行了 finally");
             return i + 2;
         }
     }
 ​
     @Test
     void testOrder() {
         System.out.println("输出了 i = " + finallyChangeVariable());
     }

最终执行的是 finally 块中的 return 方法。原因其实上面已经说了 finally 块是在 try 块中 return 方法之前执行的,执行到 finally 块中的 return 方法就返回并终止并不会再去执行 try 中的 return 方法。

29-yiyan

在技术的星河中遨游,我们互为引路星辰,共同追逐成长的光芒。愿本文的洞见能触动您的思绪,若有所共鸣,请以点赞之手,轻抚赞同的弦。

原文地址: https://www.emanjusaka.top/2024/07/java-finally-non-execution

微信公众号:emanjusaka的编程栈