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

2022-04-14 14:56:33 浏览数 (1)

1、前言

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

代码语言:javascript复制
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 的代码块一定会被执行吗?为什么?

2、问题分析

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

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

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

因此填充代码:

代码语言:javascript复制
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 的源码如下:

代码语言:javascript复制
public static void exit(int status) { 
     Runtime.getRuntime().exit(status); 
}

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

底层调用到 Runtime#exit:

代码语言:javascript复制
public void exit(int status) { 
    SecurityManager security = System.getSecurityManager(); 
    if (security != null) { 
        security.checkExit(status); 
    } 
    Shutdown.exit(status); 
}

3、延伸

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

代码语言:javascript复制
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 ,则会进行安全检查。

代码语言:javascript复制
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代码块。

代码语言:javascript复制
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"); 
        } 
    } 
}

4、总结

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

0 人点赞