2024-03-13
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1323399980

在 Java 中,无论其前面的 try 代码块是否抛出异常,finally 都会执行到,所以它通常被用来关闭资源或进行清理操作。但是确实是存在一些特殊情况,导致 finally 是不会执行到的。

详解

1、在 try 语句之前 return 或者抛出异常

    @Test
    public void finallyTest() {
        int i =  1 / 0;
        try {
            //....
        } finally {
            System.out.println("执行finally");
        }
    }
  • finally 语句被执行的必要而非充分条件是相应的 try 语句一定被执行到。

2、程序所在的线程死亡

序所在的线程在进入 finally 代码块之前死亡,例如调用 System.exit(0)finally 代码块不会被执行。

    @Test
    public void finallyTest() {
        try {
            System.out.println("执行 try 代码块");
            System.exit(0);
        } finally {
            System.out.println("执行finally");
        }
    }

System.exit(0) 是终止Java虚拟机的,JVM都停止了,自然所有的程序就都结束了,当然finally语句也就不会被执行了。

3、守护线程

如果 finally 代码块是在守护线程中,并且所有的非守护线程都已经退出,那么JVM可能在守护线程执行 finally 代码块之前退出。

public class FinallyTest {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "--执行守护线程");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                System.out.println(Thread.currentThread().getName() + "--执行守护线程 finally 代码块");
            }
        });

        thread.setDaemon(true);
        thread.start();

        Thread.sleep(1000);

        System.out.println(Thread.currentThread().getName() + "-退出");
    }
}

结果:

main 线程退出后,JVM 就会退出了,finally 就执行不到了。

扩展

下面我们通过一些示例来看看 trycatchfinally 代码块的执行顺序。

示例一

public class FinallyTest {

    public static void main(String[] args) {
        System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
    }

    public static int finallyTest() {
        try {
            System.out.println("try 代码块被执行!");

            return 1;
        } catch (Exception e) {
            System.out.println("catch 代码块被执行!");
            return 3;
        } finally {
            System.out.println("finally 代码块被执行!");
        }
    }
}

执行结果:

finally 代码块在 try 代码块 return 语句前执行。

示例二

基于上面例子改造下:

public class FinallyTest {

    public static void main(String[] args) {
        System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
    }

    public static int finallyTest() {
        try {
            System.out.println("try 代码块被执行!");
            int i = 1 / 0;
            return 1;
        } catch (Exception e) {
            System.out.println("catch 代码块被执行!");
            return 3;
        } finally {
            System.out.println("finally 代码块被执行!");
        }
    }
}

执行结果:

finally 代码块在 catch 代码块 return 语句前执行。

示例三

就要示例一,我们再改造下:

public class FinallyTest {

    public static void main(String[] args) {
        System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
    }

    public static int finallyTest() {
        try {
            System.out.println("try 代码块被执行!");
            return 1;
        } catch (Exception e) {
            System.out.println("catch 代码块被执行!");
            return 3;
        } finally {
            System.out.println("finally 代码块被执行!");
            return 2;
        }
    }
}

执行结果:

你会看到 finallyTest() 返回的是 finally 代码块的 2。这是为什么呢?这里先不解释,继续看你就会慢慢明白了。

示例四

public class FinallyTest {

    public static void main(String[] args) {
        System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
    }

    public static int finallyTest() {
        int i = 1;
        try {
            System.out.println("try 代码块被执行!");
            return i;
        } catch (Exception e) {
            System.out.println("catch 代码块被执行!");
            return 3;
        } finally {
            System.out.println("finally 代码块被执行!");
            i++;
            System.out.println("finally 代码块中的 i=" + i);
        }
    }
}

执行结果

finally 代码中的 i = 2 ,但是返回的 1。是不是有点儿奇怪,前面介绍了 finally 代码块是在 return 之前执行的,但是在 finally 中 i = 2,为什么返回的还是 1 呢?

当程序执行到 try 代码块的 return 语句时,返回值会被存储起来,但在实际返回给调用者之前,finally 代码块会被执行。如果 finally 代码块中也有一个 return 语句,那么它将覆盖 try 代码块中的返回值,并且是最终的返回结果。

示例五

public class FinallyTest {

    public static void main(String[] args) {
        System.out.println("main 执行 finallyTest 的结果 :" + finallyTest());
    }

    public static int finallyTest() {
        try {
            System.out.println("try 代码块被执行!");
            return tryTest();
        } catch (Exception e) {
            System.out.println("catch 代码块被执行!");
            return 3;
        } finally {
            System.out.println("finally 代码块被执行!");
        }
    }

    public static int tryTest() {
        System.out.println("tryTest 代码块被执行!");
        return 3;
    }
}

执行结果

这结果有没有出乎你的意料?前面不是提过,finally 里面的代码块不是比 try 代码库里面的 return 先执行吗?怎么会出现 tryTest()finally 还先执行呢?原因如下:

在Java中,当一个try代码块包含一个return语句时,这个return语句会“先执行”,但并不是立即返回。这里的“先执行”指的是确定返回值的过程会在进入finally代码块之前发生。然而,实际上在finally代码块执行完毕之前,方法不会真正返回。

最后

所以 try 里面有 return 语句时,执行过程如下:

  1. 执行 try 代码块。
  2. 执行try里面的return。当程序执行到try代码块中的return语句时,会先计算这个return语句的返回值。例如,如果返回的是一个变量的值,那么这个值会被确定下来。所以示例5 中的 tryTest() 是先于 finally 执行的。
  3. 暂停返回:尽管 try 代码块中的return语句已经“执行”了,但方法不会立即返回。而是转移到finally代码块。
  4. 执行finally代码块:finally代码块现在执行。如果finally代码块中也有return语句,它会覆盖try代码块中的返回值。
  5. 完成方法返回:如果finally代码块没有新的return语句,那么最初在try代码块中确定的返回值将被用作方法的最终返回值。如果finally代码块中有return语句,那么它的返回值会成为最终结果。

所以,记住这两点即可:

  1. try 代码块永远比 finally 代码库先执行。
  2. finally 的 return 会覆盖 try 的return。
阅读全文