try statement sentencia funciona ejemplos como catch and java recursion stack-overflow try-finally

statement - try catch finally java ejemplos



El bloque Try-finally previene StackOverflowError (6)

Aprende a rastrear tu programa:

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

Esta es la salida que veo:

[...] 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 [...]

Como puede ver, el StackOverFlow se lanza en algunas capas de arriba, por lo que puede hacer pasos de recursión adicionales hasta que llegue a otra excepción, y así sucesivamente. Este es un "bucle" infinito.

Echa un vistazo a los siguientes dos métodos:

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

La bar() ejecución bar() da como resultado claramente un StackOverflowError , pero la ejecución de foo() no lo hace (el programa parece ejecutarse indefinidamente). ¿Porqué es eso?


Cuando obtienes una excepción de la invocación de foo() dentro del try , llamas a foo() desde finally y empiezas a repetir de nuevo. Cuando eso cause otra excepción, llamarás a foo() desde otro final por finally() , y así sucesivamente casi hasta el infinito .


El programa simplemente parece funcionar para siempre; en realidad termina, pero toma exponencialmente más tiempo cuanto más espacio de pila tenga. Para probar que termina, escribí un programa que primero agota la mayor parte del espacio de pila disponible, y luego llama a foo , y finalmente escribe un rastro de lo que sucedió:

foo 1 foo 2 foo 3 Finally 3 Finally 2 foo 3 Finally 3 Finally 1 foo 2 foo 3 Finally 3 Finally 2 foo 3 Finally 3 Exception in thread "main" java.lang.Error at Main.foo(Main.java:39) at Main.foo(Main.java:45) at Main.foo(Main.java:45) at Main.foo(Main.java:45) at Main.consumeAlmostAllStack(Main.java:26) at Main.consumeAlmostAllStack(Main.java:21) at Main.consumeAlmostAllStack(Main.java:21) ...

El código:

import java.util.Arrays; import java.util.Collections; public class Main { static int[] orderOfOperations = new int[2048]; static int operationsCount = 0; static Error fooKiller; static Error wontReachHere = new Error("Won''t reach here"); static RuntimeException done = new RuntimeException(); public static void main(String[] args) { try { consumeAlmostAllStack(); } catch (RuntimeException e) { if (e != done) throw wontReachHere; printResults(); throw fooKiller; } throw wontReachHere; } public static int consumeAlmostAllStack() { try { int stackDepthRemaining = consumeAlmostAllStack(); if (stackDepthRemaining < 9) { return stackDepthRemaining + 1; } else { try { foo(1); throw wontReachHere; } catch (Error e) { fooKiller = e; throw done; //not enough stack space to construct a new exception } } } catch (Error e) { return 0; } } public static void foo(int depth) { //System.out.println("foo " + depth); Not enough stack space to do this... orderOfOperations[operationsCount++] = depth; try { foo(depth + 1); } finally { //System.out.println("Finally " + depth); orderOfOperations[operationsCount++] = -depth; foo(depth + 1); } throw wontReachHere; } public static String indent(int depth) { return String.join("", Collections.nCopies(depth, " ")); } public static void printResults() { Arrays.stream(orderOfOperations, 0, operationsCount).forEach(depth -> { if (depth > 0) { System.out.println(indent(depth - 1) + "foo " + depth); } else { System.out.println(indent(-depth - 1) + "Finally " + -depth); } }); } }

Puedes probarlo en línea! (Algunas carreras pueden llamar a foo más o menos veces que otras)


En un esfuerzo por proporcionar evidencia razonable de que esto finalmente terminará, ofrezco el siguiente código bastante sin sentido. Nota: Java NO es mi lenguaje, por mucho que lo haga la imaginación más vívida. Ofrezco esto solo para apoyar la respuesta de Peter, que es la respuesta correcta a la pregunta.

Esto intenta simular las condiciones de lo que sucede cuando una invocación NO puede suceder porque introduciría un desbordamiento de pila. Me parece que lo más difícil que las personas no comprenden es que la invocación no sucede cuando no puede suceder.

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("@try("+n+")"); } finally { System.out.println(n + " - Finally"); if (n < limit) foo(n+1,limit); else throw new Exception("@finally("+n+")"); } } }

La salida de esta pequeña pila sin sentido de goo es la siguiente, y la excepción real capturada puede ser una sorpresa; Ah, y 32 llamadas de prueba (2 ^ 5), que es totalmente esperado:

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: @finally(5)


Intenta ejecutar el siguiente código:

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

Encontrará que el bloque finalmente se ejecuta antes de lanzar una Excepción hasta el nivel superior. (Salida:

Finalmente

Excepción en el hilo "main" java.lang.Exception: TEST! en test.main (test.java:6)

Esto tiene sentido, ya que finalmente se llama justo antes de salir del método. Esto significa, sin embargo, que una vez que obtenga el primer Error , intentará lanzarlo, pero el último debe ejecutarse primero, por lo que se ejecuta de nuevo foo() , lo que genera otro desbordamiento de pila, y como tal, finalmente se ejecuta nuevamente. Esto sigue sucediendo para siempre, por lo que la excepción nunca se imprime realmente.

Sin embargo, en su método de barra, tan pronto como se produce la excepción, se lanza directamente al nivel superior y se imprimirá


No funciona para siempre. Cada desbordamiento de pila hace que el código se mueva al bloque final. El problema es que llevará mucho, mucho tiempo. El orden del tiempo es O (2 ^ N) donde N es la profundidad máxima de pila.

Imagina que la profundidad máxima es 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()

Para trabajar cada nivel en el bloque final, tome el doble de tiempo que la profundidad de la pila podría ser 10,000 o más. Si puedes hacer 10,000,000 llamadas por segundo, esto tomará 10 ^ 3003 segundos o más que la edad del universo.