最后有时候让我感到困惑

21

今天在大学里我们谈到了一些关于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块之后继续运行。


3
如果您的代码捕获到异常后,执行了类似于抛出另一个(可能未经检查的)异常的操作,那么out.close()会在这种情况下被执行吗?换句话说,仅仅打印堆栈跟踪并继续执行并不总是正确处理异常的方式。 - rmlan
3
如果你重新抛出异常,或者没有全部捕获它,那么finally块就是你的朋友。还有一些不是Exception的Throwable条件,那么第二个例子会有问题。 - KevinO
3
通常捕获Exception并不是一个好主意。你应该尽可能地捕获最具体的异常类型。 - Andy Turner
1
没有人在谈论AutoCloseable!! - Not a bug
@AndyTurner 是的,我知道。今天我们学习了异常处理。这只是用来解释我的真正问题的。 - Miljan Rakita
显示剩余2条评论
8个回答

33

如果代码抛出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 块中也可能发生错误。谢谢,现在我明白了。 - Miljan Rakita
16
错误随时可能发生,这也是其中的乐趣之一! - Gusdor
2
请记住,finally在真正的灾难性情况下(比如电源故障)不会被执行,因此不要依赖它来维护数据库事务的一致性。 - Mark

3
如果try块中发生未捕获的错误,甚至在catch块中发生错误,catch后面的代码将不会被执行,但finally块将会执行。
“finally”将始终被执行。
来自Java文档:
当try块退出时,finally块总是执行。这确保了即使出现意外异常,finally块也会被执行。但finally不仅仅用于异常处理——它允许程序员避免清理代码被返回、继续或中断所绕过。将清理代码放入finally块中始终是一种良好的实践,即使没有预期到任何异常。

2

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

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


2

如果catch块中有东西抛出异常,out.close将不会执行。您还可以使用“带资源的try”来确保在使用完后所有资源都已关闭。请尝试以下示例:

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();
    }

非常清晰!非常感谢! - Miljan Rakita

2

在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块内的代码会在方法中可能离开的所有地方被复制。任何带有Finally但没有全捕获处理程序的try块等同于一个立即抛出异常的带有全捕获处理程序的try块(处理器可以在全捕获处理程序之前插入Finally代码的副本)。


1

如果PrintWriter的构造函数发生异常,你的第二个示例可能会抛出一个意外的NullPointerException

另一个可能性是out.close();会抛出一个未被捕获的错误。

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


非常感谢。我不知道可以抛出异常! - Miljan Rakita
1
即使在第一个示例中,也会抛出NPE异常。最好使用try-with-resources。 - Axel
是的,try-with-resources更好,但问题是关于finally,所以我没有提到它。 - Polygnome

1
尽管它们本身并不是完整的答案,但这两个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

原因可以在JLS第14.20.2章中找到。

一个带有finally块的try语句首先执行try块。然后有一个选择:
• 如果由于值V的抛出而使try块的执行突然终止,则有一个选择:
[...]
- 如果V的运行时类型与try语句的任何catch子句的可捕获异常类不兼容,则执行finally块。 然后有一个选择:
[...]
如果finally块因原因S而突然终止,则try语句因原因S而突然终止(并且忘记丢弃值V)。
如果try块因任何其他原因R而突然终止,则执行finally块,然后有一个选择:
- 如果finally块正常完成,则try语句以原因R突然终止。
- 如果finally块因原因S而突然终止,则try语句因原因S而突然终止(原因R被丢弃)。 考虑到 return 是完成 tryfinally 块的一种突然方式。

0
一个更小的例子:
PrintWriter out = null;
try {
    out = new PrintWriter();
    out.print(data);
} finally {
    out.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部分抛出其他未被捕获的异常,但是finally块仍然会执行,然后代码跳转到调用者中的下一个封闭的try块。

编写finally块时要小心 - 它不在try块内,因此任何异常都会优先于正在进行的异常。简短的建议是尽量避免在finallycatch块内可能引发异常的操作。


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