Java中finally块的要点是什么?

8
我认为以下的例子很有帮助,但是我不知道finally块的重要性。你能告诉我这两个代码样例执行的区别吗?同时,一个现实生活中的例子会更有帮助。
样例1:
    try{
       // some code 1
    }catch(Exception ex){
       // print exception   
    }finally{
       // some code 2            
    }

样例2:
    try{
      // some code 1
    }catch(Exception ex){
      // print exception   
    }
    // some code 2

请注意,如果您不想要混乱的代码,很少有必要在try语句中同时使用catchfinally - Tom Hawtin - tackline
@Tom 为什么呢?它们有着完全不同的用途,catch 用于处理失败,finally 用于资源处理。 - james
@james 是的,它们有完全不同的用途,这意味着它们的范围通常也应该不同。 - Tom Hawtin - tackline
7个回答

17
你呈现的两个代码片段有很大的区别,例如当catch块本身抛出异常时,由于其语义,finally块仍将被执行。
因此,以下代码片段会打印"Finally!",而不是"What about me???":
    try {
        throw null;     // throws NullPointerException!
    } catch (Exception e) {
        int oops = 1/0; // throws ArithmeticException!
    } finally {
        System.out.println("Finally!"); // still gets executed!
    }
    System.out.println("What about me???"); // doesn't get executed!

一般来说,try块的finally几乎总是会被执行。但是对于跟在try块后面的任何代码都没有这样的保证。

但是如果我的catch块只是一个简单的print语句呢?

仍然不能保证它不会抛出异常。例如,在构建异常详细信息时仍可能发生错误。
即使您尽最大努力保证catch代码是“安全的”,并且紧随try语句后面的代码将始终被执行,那么问题就变成了“为什么要这样做?” 为什么要避免使用finally,但又试图复制其语义? finally的语义是有保证的,无论代码的编写者还是读者都不需要承担证明的负担。正是因为如此,使用finally块来放置强制性的“清理”代码是惯用法。使用finally可以保证正确性,并提高可写性和可读性。

明白了。如果我在 catch 块中只调用 print 函数,那么 finally 就没有意义了吗? - abuzittin
@abuzittin:除了语义本身,模式本身也很重要。在finally中放置强制清理代码是惯用法。finally的语义使意图更加清晰。此外,在某些情况下,简单的打印可能会引发异常(例如IOException、记录器失控、消息构建出错等)。 - polygenelubricants
1
请注意,如果发生灾难性事件(例如:用户终止进程或拔掉机器),它仍可能无法执行,但就异常而言,您是安全的。 - David X

8
finally块即使在抛出未被catch块捕获的Error时也会执行。因此,您可以将清理代码放在finally块中,该代码应始终运行,而不管trycatch块中的操作结果如何。
请注意,通常catch块捕获更具体类型的异常 - 通常仅捕获已检查的异常 - 因此,在大多数情况下,上面两个代码示例之间的区别非常明显。
更新:您可能会说,您的catch块永远不会引发异常,因此不需要finally。但是,请注意以下两点:
  • 这只是代码的当前状态,它可能会在将来发生改变 - 您能保证将来添加一些潜在引发异常的代码到catch块的程序员会记得将其后面的清理代码放入finally块中吗?
  • try-catch-finally是一种编程惯用语,它使人们更容易理解代码正在发生什么。如果您不使用常见的惯用语,就会存在误解的风险,从而导致长期的错误。

  • 异常或错误。如果我们用错误代替异常重新思考我的问题,你的答案会是什么? - abuzittin
    @abuzittin,与此同时,我已经更新了我的帖子,我相信这回答了你的问题。顺便说一下,应该是Throwable而不是Error - Péter Török
    避免使用 finally 并尝试复制其语义是没有意义的,因为 finally 更符合惯用语。 - polygenelubricants

    2
    你可以使用finally块来清理和运行任何代码,无论是否抛出(并捕获)异常。这包括在catch块中的代码。

    你的意思是,如果我有捕获块代码可以抛出异常?在我只调用打印函数的情况下,最终语句就没有重要性了吗? - abuzittin

    1

    当我们想要释放try块中使用的资源时,finally块是唯一一个可以在任何情况下执行它们而不会遗漏的地方。因为如果抛出异常,Java不会执行紧随其后的代码,而是直接跳转到catch块。


    0
    在现实生活中,finally块用于关闭已打开的资源,即使发生异常也是如此。例如,当您读取(或写入)文件,访问数据库等时。
    public void readFile(String fileName) {
        FileReader fr;
        BufferedFileReader bfr;
    
        try {
            fr = new FileReader(fileName);
            bfr = new BufferedFileReader(fr);
            // ...
        } catch (IOException ioe) {
            // ...
        } finally {
            // TO ENSURE THAT THE READERS ARE CLOSED IN ALL CASES
            if (bfr != null) { 
                try {
                    bfr.close();
                } catch (IOException ignoredIOE) {}
            }
            if (fr != null) { 
                try {
                    fr.close();
                } catch (IOException ignoredIOE) {}
            }
        }
    }
    

    0
    请注意,你可以即使没有catch语句也有try-finally块:
    try{
       // some code 
    }finally{
       // cleanup code
    }
    

    一个例子是一个方法想要将异常传播给调用者,但仍然需要清理代码,比如释放一个锁。

    0

    如果try块中的语句抛出未经检查的异常,finally块将被执行,允许程序员采取相关操作。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接