Java 的 finally 代码块的代码一定会执行吗?

简介: Java 的 finally 代码块的代码一定会执行吗?

一、背景

对于很多初学者而言,会想当然地认为 “finally 代码块一定会被执行”,因此我们可以看下面这个案例:


public class Demo {

   public static void main(String[] args) {

       try {

           BufferedReader br = new BufferedReader(new FileReader("file.txt"));

           System.out.println(br.readLine());

           br.close();

       } catch (IOException e) {

           // 省略一些代码

       } finally {

           System.out.println("Exiting the program");

       }

   }

}

问题是:该段代码 finally 的代码块一定会被执行吗?为什么?


二、分析

通常实际编码时,捕获异常后会记录日志或者将异常抛出等,此时 finally 代码块一般肯定会被执行到。


那么如何才能不执行finally呢?


于是我们想到,如果让虚拟机退出,问题不就解决了吗? (就是这么暴力)


因此填充代码:


public class Demo {

   public static void main(String[] args) {

       try {

           BufferedReader br = new BufferedReader(new FileReader("file.txt"));

           System.out.println(br.readLine());

           br.close();

       } catch (IOException e) {

          System.exit(2);

       } finally {

           System.out.println("Exiting the program");

       }

   }

}

如果捕获到 IO异常,则会执行  虚拟机退出指令,则不会执行finally 代码块。


System#exit 的源码如下:


   /**

    * Terminates the currently running Java Virtual Machine. The

    * argument serves as a status code; by convention, a nonzero status

    * code indicates abnormal termination.

    * <p>

    * This method calls the <code>exit</code> method in class

    * <code>Runtime</code>. This method never returns normally.

    * <p>

    * The call <code>System.exit(n)</code> is effectively equivalent to

    * the call:

    * <blockquote><pre>

    * Runtime.getRuntime().exit(n)

    * </pre></blockquote>

    *

    * @param      status   exit status.

    * @throws  SecurityException

    *        if a security manager exists and its <code>checkExit</code>

    *        method doesn't allow exit with the specified status.

    * @see        java.lang.Runtime#exit(int)

    */

   public static void exit(int status) {

       Runtime.getRuntime().exit(status);

   }

通过注释我们可以了解到, 当 status 为 非0 时,表示异常退出。


底层调用到 Runtime#exit:


   /**

    * Terminates the currently running Java virtual machine by initiating its

    * shutdown sequence.  This method never returns normally.  The argument

    * serves as a status code; by convention, a nonzero status code indicates

    * abnormal termination.

    *

    * <p> The virtual machine's shutdown sequence consists of two phases.  In

    * the first phase all registered {@link #addShutdownHook shutdown hooks},

    * if any, are started in some unspecified order and allowed to run

    * concurrently until they finish.  In the second phase all uninvoked

    * finalizers are run if {@link #runFinalizersOnExit finalization-on-exit}

    * has been enabled.  Once this is done the virtual machine {@link #halt

    * halts}.

    *

    * <p> If this method is invoked after the virtual machine has begun its

    * shutdown sequence then if shutdown hooks are being run this method will

    * block indefinitely.  If shutdown hooks have already been run and on-exit

    * finalization has been enabled then this method halts the virtual machine

    * with the given status code if the status is nonzero; otherwise, it

    * blocks indefinitely.

    *

    * <p> The <tt>{@link System#exit(int) System.exit}</tt> method is the

    * conventional and convenient means of invoking this method. <p>

    *

    * @param  status

    *         Termination status.  By convention, a nonzero status code

    *         indicates abnormal termination.

    *

    * @throws SecurityException

    *         If a security manager is present and its <tt>{@link

    *         SecurityManager#checkExit checkExit}</tt> method does not permit

    *         exiting with the specified status

    *

    * @see java.lang.SecurityException

    * @see java.lang.SecurityManager#checkExit(int)

    * @see #addShutdownHook

    * @see #removeShutdownHook

    * @see #runFinalizersOnExit

    * @see #halt(int)

    */

   public void exit(int status) {

       SecurityManager security = System.getSecurityManager();

       if (security != null) {

           security.checkExit(status);

       }

       Shutdown.exit(status);

   }


三、延伸

同样的问题,请看下面代码片段:


public class Demo {

   public static void main(String[] args) {

     // 一些代码

       try {

           BufferedReader br = new BufferedReader(new FileReader("file.txt"));

           System.out.println(br.readLine());

           br.close();

       } catch (IOException e) {

          System.exit(2);

       } finally {

           System.out.println("Exiting the program");

       }

   }

}

问题是:如果try 代码块部分发生IO异常,是否一定不会执行到 finally 代码块呢?


what?  上面不是说不会执行吗?


我们再仔细看上面给出的  Runtime#exit 源码,可以发现,如果 SecurityManager 不为 null ,则 会进行安全检查。


public void exit(int status) {

// 如果有securityManager , 则调用 checkExit函数

       SecurityManager security = System.getSecurityManager();

       if (security != null) {

           security.checkExit(status);

       }

// 检查通过后退出

       Shutdown.exit(status);

   }

安全检查通过才会执行   Shutdown#exit 执行最终的虚拟机退出。


因此如果我们可以修改 SecurityManager 如果检查退出时抛出异常,那么在 执行  System.exit(2) 时就会发生异常,最终依然会执行到 finally代码块。


public class Demo {

   public static void main(String[] args) {

       // 修改 SecurityManager

       System.setSecurityManager(new SecurityManager() {

           @Override

           public void checkExit(int status) {

               throw new SecurityException("不允许退出");

           }

       });

       try {

           BufferedReader br = new BufferedReader(new FileReader("file.txt"));

           System.out.println(br.readLine());

           br.close();

       } catch (IOException e) {

           System.exit(2);

       } finally {

           System.out.println("Exiting the program");

       }

   }

}
/


四、总结

学习时一定要抱着不满足的心态,这样才能有机会学的更加深入,理解地更好。




相关文章
|
6月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
469 7
|
6月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
336 115
|
6月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
244 98
|
6月前
|
Java 编译器 API
java最新版和java8的区别,用代码展示
java最新版和java8的区别,用代码展示
532 43
|
6月前
|
安全 Java 容器
告别空指针噩梦:Optional让Java代码更优雅
告别空指针噩梦:Optional让Java代码更优雅
489 94
|
6月前
|
安全 Java 容器
告别繁琐判空:Optional让你的Java代码更优雅
告别繁琐判空:Optional让你的Java代码更优雅
|
7月前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
927 3
|
Java 数据安全/隐私保护
Java代码的执行顺序和构造方法
构造方法是类的一种特殊方法,用于初始化新对象。在 Java 中,每个类默认都有一个与类名同名的构造方法,无需返回类型。构造方法不能用 static、final、synchronized、abstract 或 native 修饰。它可以重载,通过不同的参数列表实现多种初始化方式。构造方法在对象实例化时自动调用,若未显式声明,默认提供一个无参构造方法。构造代码块和静态代码块分别用于对象和类的初始化,按特定顺序执行。
264 0
|
Java
Java代码的执行顺序
Java代码的执行顺序
226 1
LearnJava(四) | Java代码块执行顺序测试
最近笔试常常遇到考察Java代码块执行顺序的题目,网上查看博客错漏百出,特地自己测试了一下。 如有错漏,希望路过的大佬指出来,以便我进行更改。   先上代码吧! public class ClassA { private static St...
1078 0
下一篇
开通oss服务