Try-finally块可以防止StackOverflowError

看看以下两种方法:

public static void foo() { try { foo(); } finally { foo(); } } public static void bar() { bar(); } 

运行bar()显然会导致一个StackOverflowError ,但运行foo()不(程序似乎无限期运行)。 这是为什么?

它不会永远运行。 每个堆栈溢出导致代码移动到finally块。 问题是,这将需要非常长的时间。 时间顺序是O(2 ^ N),其中N是最大堆栈深度。

想象一下,最大的深度是5

 foo() calls foo() calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally calls foo() calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally calls foo() calls foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() finally foo() calls foo() which fails to call foo() finally calls foo() which fails to call foo() 

为了使每个级别进入finally块需要两倍的堆栈深度可能是10,000或更多。 如果每秒可以拨打10,000,000个电话,那么这个时间将比宇宙年龄多10 ^ 3003秒或更长。

当你在try中调用foo()发生exception,你可以从finally调用foo() ,然后再次recursion。 当这导致另一个exception,你会从另一个内部finally()调用foo() finally() ,等等几乎是无限的

尝试运行以下代码:

  try { throw new Exception("TEST!"); } finally { System.out.println("Finally"); } 

你会发现finally块在抛出一个Exception之前执行到它上面的级别。 (输出:

最后

线程“main”java.lang.Exception中的exception:TEST! 在test.main(test.java:6)

这是有道理的,因为在退出该方法之前最后被调用。 然而,这意味着,一旦你得到了第一个StackOverflowError ,它就会尝试抛出它,但是finally必须先执行,所以它会再次运行foo() ,从而得到另一个堆栈溢出,并最终再次运行。 这种情况一直在发生,所以这个例外从来不会被打印出来。

然而,在你的栏方法中,一旦发生exception,它就会直接抛到上面的层次,并被打印出来

为了提供合理的证据表明这个WILL最终会终止,我提供了下面这些毫无意义的代码。 注意:Java是不是我的语言,通过任何一个最生动的想象。 我提出这个问题只是为了支持彼得的答案,这是对这个问题正确答案。

这试图模拟调用不可能发生时发生的情况,因为它会引起堆栈溢出。 在我看来,人们没有把握的最难的事情是,在不可能发生的时候,调用不会发生。

 public class Main { public static void main(String[] args) { try { // invoke foo() with a simulated call depth Main.foo(1,5); } catch(Exception ex) { System.out.println(ex.toString()); } } public static void foo(int n, int limit) throws Exception { try { // simulate a depth limited call stack System.out.println(n + " - Try"); if (n < limit) foo(n+1,limit); else throw new Exception("StackOverflow@try("+n+")"); } finally { System.out.println(n + " - Finally"); if (n < limit) foo(n+1,limit); else throw new Exception("StackOverflow@finally("+n+")"); } } } 

这一小坨咕咕的输出是下面的,实际发生的exception可能会令人惊讶; 哦,还有32次尝试(2 ^ 5),这是完全可以预料的:

 1 - Try 2 - Try 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 2 - Finally 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 1 - Finally 2 - Try 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 2 - Finally 3 - Try 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally 3 - Finally 4 - Try 5 - Try 5 - Finally 4 - Finally 5 - Try 5 - Finally java.lang.Exception: StackOverflow@finally(5) 

学习跟踪你的程序:

 public static void foo(int x) { System.out.println("foo " + x); try { foo(x+1); } finally { System.out.println("Finally " + x); foo(x+1); } } 

这是我看到的输出:

 [...] foo 3439 foo 3440 foo 3441 foo 3442 foo 3443 foo 3444 Finally 3443 foo 3444 Finally 3442 foo 3443 foo 3444 Finally 3443 foo 3444 Finally 3441 foo 3442 foo 3443 foo 3444 [...] 

正如你所看到的,StackOverFlow抛出了上面的一些层次,所以你可以做额外的recursion步骤,直到你遇到另一个exception,依此类推。 这是一个无限的“循环”。