【问题标题】:Finally sometimes confuse me [duplicate]最后有时让我感到困惑[重复]
【发布时间】:2016-08-20 08:08:33
【问题描述】:

今天在大学里,我们谈到了trycatchfinally。 我对这两个例子感到困惑:

PrintWriter out = null;
try {
  out = new PrintWriter(...); // We open file here
} catch (Exception e) {
  e.printStackTrace();
} finally { // And we close it here
  out.close();
}

finally 中关闭文件和我们只是这样做有什么区别:

PrintWriter out = null;
try {
  out = new PrintWriter(...); // We open file here
} catch (Exception e) {
  e.printStackTrace();
}
out.close();

catch 之后的这段代码会一直执行。

你能给我一些很好的例子来说明我们使用finally和我们把代码放在catch之后的区别吗?我知道 finally 会一直执行,但是程序也会在 catch 块之后继续运行。

【问题讨论】:

  • 如果你的 catch 做了类似抛出另一个(可能未经检查的)异常的事情怎么办? out.close() 会在那种情况下运行吗?换句话说,仅仅打印堆栈跟踪并继续前进并不总是处理异常的方式。
  • 如果你重新抛出异常,或者没有全部捕获,那么 finally 块就是你的朋友。还有不是Exceptions的Throwable条件,那么第二个例子就会有问题。
  • 我即将开始学习 Throwable,所以我想我需要先学习它才能理解这一点,对吧?
  • 另外:catch Exception 通常是个坏主意。您应该捕获最具体的异常类型。
  • 没人在谈论AutoCloseable!!

标签: java exception exception-handling try-catch finally


【解决方案1】:

一个较小的例子:

PrintWriter out = null;
try {
    out = new PrintWriter();
    out.print(data);
} finally {
    out.close();
}

在这里,我们没有捕获任何异常(由调用者处理),但我们确实希望 close 作者是否我们

  • 通过try 块正常运行,或
  • 通过异常退出。

在这两种情况下,finally 中的代码都是作为离开块的一部分执行的。


在这里,我们捕获了一部分异常:

PrintWriter out = null;
try {
    out = new PrintWriter();
    out.print(data);
} catch (IOException e) {
    log(e);
} finally {
    out.close();
}
do_something_else();

这里有三种可能的路径:

  • 正常执行try部分,然后是finally,然后是do_something_else()
  • try 部分抛出 IOException,被捕获并记录,然后运行 ​​finally 块,然后运行 ​​do_something_else(),或者
  • try 部分抛出不同的 throwable,它未被捕获,但 finally 块运行,然后代码跳转到下一个封闭的 try,可能在调用者中。

在编写finally 块时要小心——它不在try 内,因此那里的任何异常都会先于任何正在进行的异常。简短的建议是尽量避免可能从finallycatch 内部抛出的东西。

【讨论】:

    【解决方案2】:

    如果代码抛出Error,它仍然会有所不同。这不会在代码中捕获,因此try/catch/finally 之后的任何部分都不会被捕获。如果它是finally 的一部分,即使对于Error,它仍然会执行。

    其次,如果由于某种原因e.printStackTrace() 抛出异常(尽管这种情况非常罕见),同样的情况也会发生 - finally 仍将被执行。

    通常finally 是一种非常安全的释放资源的方式,无论发生什么情况。从 Java 7 开始支持 try-with-resources 更加安全,因为它可以轻松管理关闭操作期间可能引发的多个异常。在这个例子中,它看起来像:

    try (PrintWriter out = new PrintWriter(...)) {
        // do whatever with out
    }
    catch (Exception e) {
        e.print... (whatever)
    }
    // no need to do anything else, close is invoked automatically by try block
    

    编辑:另请注意,您的代码并不真正正确(无论哪个版本)。如果PrintWriter 构造函数抛出异常,则out.close() 行将在NullPointerException 上失败。

    【讨论】:

    • 不知道 catch 块中可能会发生错误。谢谢,现在说得通了。
    • @MiljanRakita 错误可能发生在任何地方。这是乐趣的一部分!
    • 请记住,finally不会在真正的灾难性情况下(例如,电源故障)执行,所以不要指望它来维护数据库等事情事务一致性。
    【解决方案3】:

    虽然自己没有完整的答案,但try-finally(错误)使用的这两个示例可能会有所启发:

    public class JavaApplication3 
    {
        static int foo()
        {
            try
            {
                return 6;
            }
            finally
            {
                return 4;
            }
    
        }
    
        public static void main(String[] args)
        {
            System.out.println("foo: " + foo());
        }
    }
    
    
    public class JavaApplication3 
    {
        static int foo()
        {
            try
            {
                throw new Exception();
            }
            finally
            {
                return 4;
            }
    
        }
        public static void main(String[] args)
        {
            System.out.println("foo: " + foo());
        }
    }
    

    两个程序都输出4

    原因可以在Chapter 14.20.2 of JLS找到

    带有 finally 块的 try 语句通过首先执行 try 块来执行。然后有一个选择:

    • 如果 try 块的执行由于抛出一个值而突然完成 V,那么有一个选择:
    [...]
    – 如果 V 的运行时类型与可捕获的分配不兼容 try 语句的任何 catch 子句的异常类,然后是 finally 块被执行。然后有一个选择:
    [...]
    如果 finally 块由于原因 S 突然完成,则 try 语句 由于原因 S 突然完成(并且抛出值 V 被丢弃并且 忘记了)。

    如果 try 块的执行由于任何其他原因突然完成 R,则执行 finally 块,然后有一个选择:
    – 如果 finally 块正常完成,则 try 语句完成 突然因为 R.
    如果 finally 块由于原因 S 突然完成,则 try 语句 由于原因 S 突然完成(并且原因 R 被丢弃)。

    编辑我的

    考虑到return 是完成tryfinally 块的一种突然方式。

    【讨论】:

      【解决方案4】:

      如果try 块内发生未捕获的错误,或者即使您的catch 块内发生错误,catch 之后的“代码段”也不会执行,但finally 块将.

      finally 将始终被执行。

      来自 Java 文档:

      finally 块总是在 try 块退出时执行。这 确保即使出现意外情况也会执行 finally 块 发生异常。但 finally 不仅仅对异常有用 处理——它允许程序员避免清理代码 意外地被返回、继续或中断绕过。进行清理 finally 块中的代码始终是一个好习惯,即使没有 预计会有例外情况。

      【讨论】:

        【解决方案5】:

        在Java中,源代码:

        void foo()
        {
          try {
            if (W())
              return;
          }
          catch (FooException ex) {
            if (X())
              throw;
          }
          finally {
            Y();
          }
          Z();
        }
        

        会被编译器转换成:

        void foo()
        {
          try {
            if (W()) {
              Y();
              return;
            }
          }
          catch (FooException ex) {
            if (X()) {
              Y();
              throw;
            }
          }
          catch {
            Y();
            throw;
          }
          Y();
          Z();
        }
        

        效果是导致 finally 块中的代码在 控制可能离开方法的所有地方。任何try 块 它有一个 finally 但不是 catch-all 处理程序等价于一个具有立即抛出的 catch-all 处理程序(然后处理器可以在 catch-all 处理程序之前插入 finally 代码的副本。

        【讨论】:

          【解决方案6】:

          finally 的正常用例是当您不想在同一方法中捕获异常时。

          在这种情况下,您使用 try 和 finally 块而没有捕获。这样您就可以确保关闭您的资源,而不必在方法本身中捕获异常。

          【讨论】:

            【解决方案7】:

            如果 catch 块中的某些东西会抛出 Exception 怎么办? out.close 不会执行。您还可以使用“try with resources”确保所有资源在使用后关闭。试试这个例子:

            public static void withFinnaly() {
                    try {
                        throwException();
                        System.out.println("This won't execute");
                    } catch (Exception e) {
                        System.out.println("Exception is caught");
                        throwException();
                    } finally {
                        System.out.println("Finally is always executed," +
                                " even if method in catch block throwed Exception");
                    }
                }
            
                public static void withOutFinnaly() {
                    try {
                        throwException();
                        System.out.println("This won't execute");
                    } catch (Exception e) {
                        System.out.println("Exception is caught");
                        throwException();
                    }
                    System.out.println("Looks like we've lost this... " +
                            "This wont execute");
                }
            
                public static void throwException() throws RuntimeException {
                    throw new RuntimeException();
                }
            

            【讨论】:

            • 非常清楚!非常感谢!
            【解决方案8】:

            如果PrintWriter 的构造函数中发生异常,您的第二个示例可能会抛出不需要的NullPointerException

            另一种可能是out.close(); 会抛出一个错误,没有被捕获。

            如果您将代码移动到 finally 块中,它将始终被执行 - 无论 try 块是否成功。如果您的try-block 引发捕获的异常,这特别很有用。在您的第二个示例中,这将导致 out.close() 不被执行,而对于 finally 块,即使 try 块抛出未捕获的错误,它也会被执行。

            【讨论】:

            • 非常感谢。不知道有可能抛出异常!
            • 即使在第一个示例中,也会抛出 NPE。最好是使用 try-with-resources。
            • 是的,try-with-resources 更好,但问题是关于finally,所以我没有提及。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-10-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多