¿No se detectó la excepción de Java?

170

Tengo un pequeño problema teórico con las construcciones try-catch.

Ayer tomé un examen práctico sobre Java y no entiendo el siguiente ejemplo:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

La pregunta era "¿cómo se verá la salida?"

Estaba bastante seguro de que sería AB2C3, PERO sorpresa sorpresa, no es cierto.

La respuesta correcta es ABC3 (probado y realmente es así).

Mi pregunta es, ¿a dónde se fue la Excepción ("2")?

Kousalik
fuente
8
+1 Ahh hombre, sabía esta respuesta. Me preguntaron esto en una entrevista. Es una muy buena pregunta para entender cómo funciona try / catch / finalmente en la pila.
Pero no soy una clase de envoltura
10
Solo hay una declaración de impresión que podría imprimir un número (la última:) print(e.getMessage()). Pensaste que la salida sería AB2C3: ¿creías que el catchbloque más externo se ejecutaría dos veces?
Adrian Pronk
En Java, antes de que se ejecute una instrucción que transfiere el control fuera del bloque catch, el bloque finalmente se ejecuta siempre que exista. Si solo el código en el bloque finalmente no transfiere el control al exterior, la instrucción retrasada del bloque catch se ejecuta.
Thomas

Respuestas:

198

De la especificación del lenguaje Java 14.20.2. :

Si el bloque catch se completa abruptamente por la razón R, entonces el bloque finalmente se ejecuta. Entonces hay una opción:

  • Si el bloque finalmente se completa normalmente, entonces la instrucción try se completa abruptamente por la razón R.

  • Si el bloque finalmente se completa abruptamente por la razón S, entonces la instrucción try se completa abruptamente por la razón S (y la razón R se descarta) .

Entonces, cuando hay un bloque catch que arroja una excepción:

try {
    // ...
} catch (Exception e) {
    throw new Exception("2");
}

pero también hay un bloque finalmente que también arroja una excepción:

} finally {
    throw new Exception("3");
}

Exception("2")será descartado y solo Exception("3")se propagará.

Adam Siemion
fuente
72
Esto incluso es válido para las returndeclaraciones. Si su bloque finalmente tiene un retorno, anulará cualquier retorno en un bloque tryo catch. Debido a estas "características", una buena práctica es que finalmente block nunca debe lanzar una excepción o tener una declaración return.
Augusto
Esta es también la ventaja heredada que try-with-resources tiene en Java 7. Conserva la excepción inicial si se genera una excepción secundaria al cerrar los recursos, por lo general, facilita la depuración.
w25r
19

Las excepciones lanzadas finalmente en el bloque suprimen la excepción lanzada anteriormente en el bloque try o catch.

Ejemplo de Java 7: http://ideone.com/0YdeZo

Del ejemplo de Javadoc :


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

Sin embargo, en este ejemplo, si los métodos readLine y cierran ambas excepciones de lanzamiento, entonces el método readFirstLineFromFileWithFinallyBlock arroja la excepción lanzada desde el último bloque; se suprime la excepción lanzada desde el bloque try.


La nueva try-withsintaxis de Java 7 agrega otro paso de supresión de excepciones: las excepciones lanzadas en el bloque try suprimen las lanzadas anteriormente en la parte try-with.

del mismo ejemplo:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

Se puede lanzar una excepción desde el bloque de código asociado con la instrucción try-with-resources. En el ejemplo anterior, se puede lanzar una excepción desde el bloque try, y se pueden lanzar hasta dos excepciones desde la instrucción try-with-resources cuando intenta cerrar los objetos ZipFile y BufferedWriter. Si se lanza una excepción desde el bloque try y se lanzan una o más excepciones desde la declaración try-with-resources, entonces se suprimen esas excepciones lanzadas desde la declaración try-with-resources, y la excepción lanzada por el bloque es la eso es arrojado por el método writeToFileZipFileContents. Puede recuperar estas excepciones suprimidas llamando al método Throwable.getSuppressed desde la excepción lanzada por el bloque try.


En el código de la pregunta, cada bloque descarta la antigua excepción, ni siquiera lo registra, lo que no es bueno cuando intenta resolver algunos errores:

http://en.wikipedia.org/wiki/Error_hiding

Dakota del Sur
fuente
9

Como throw new Exception("2");se arroja desde el catchbloque y no try, no se volverá a atrapar.
Ver 14.20.2. Ejecución de try-finally y try-catch-finally .

Esto es lo que está sucediendo:

try {
    try {
        System.out.print("A");         //Prints A
        throw new Exception("1");   
    } catch (Exception e) { 
        System.out.print("B");         //Caught from inner try, prints B
        throw new Exception("2");   
    } finally {
        System.out.print("C");         //Prints C (finally is always executed)
        throw new Exception("3");  
    }
} catch (Exception e) {
    System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
}
Maroun
fuente
Sí, es cierto, veo que esto está sucediendo, pero estaba buscando una explicación, por qué se comporta de esta manera
Kousalik
5

Su pregunta es muy obvia, y la respuesta es simple en la misma medida. El objeto de excepción con mensaje como "2" se sobrescribe con el objeto de excepción con mensaje como "3".

Explicación: Cuando ocurre una Excepción, su objeto se lanza para atrapar bloque para manejar. Pero cuando ocurre una excepción en el bloque catch, su objeto se transfiere al bloque OUTER CATCH (si corresponde) para el manejo de excepciones. Y lo mismo sucedió aquí. El objeto de excepción con el mensaje "2" se transfiere al bloque de captura OUTER. Pero espere ... Antes de salir del bloque try-catch interno, TIENE QUE EJECUTAR FINALMENTE. Aquí ocurrió el cambio que nos preocupa. Se arroja un nuevo objeto EXCEPTION (con el mensaje "3") o finalmente este bloque que reemplazó al objeto Exception ya lanzado (con el mensaje "2"). Como resultado de esto, cuando se imprime el mensaje del objeto Exception, tenemos valor anulado, es decir, "3" y no "2".

Keep Remember: solo se puede manejar un objeto de excepción en el bloque CATCH.

Bharat
fuente
2

El finallybloque siempre corre. O returndesde dentro del bloque try o se lanza una excepción. La excepción lanzada en el finallybloque anulará la lanzada en la rama catch.

Además, lanzar una excepción no causará ningún resultado por sí solo. La línea throw new Exception("2");no escribirá nada.

allprog
fuente
1
Sí, sé que lanzar Exception no produce nada por sí mismo, pero no veo la razón por la que se debe descartar la Excepción 2. Soy un poco más inteligente de nuevo :-)
Kousalik
siempre es mucho tiempo y en mucho tiempo puede pasar cualquier cosa (ver rompecabezas wouter.coekaerts.be/2012/puzzle-dreams )
Dainius
0

De acuerdo a su código:

try {
    try {
        System.out.print("A");
        throw new Exception("1");   // 1
    } catch (Exception e) {
        System.out.print("B");      // 2
        throw new Exception("2");
    } finally {                     // 3
        System.out.print("C");      // 4 
        throw new Exception("3");
    }
} catch (Exception e) {             // 5
    System.out.print(e.getMessage());
}

Como puedes ver aquí:

  1. imprime A y lanza una excepción # 1;
  2. esta excepción ha sido atrapada por la declaración catch e print B - # 2;
  3. el bloque finalmente se # 3ejecuta después de la declaración try-catch (o solo try, si no se ha producido ninguna excepción) e imprime C - # 4y lanza una nueva excepción;
  4. este ha sido atrapado por la declaración de captura externa # 5;

El resultado es ABC3. Y 2se omite de la misma manera que1

nazar_art
fuente
Lo sentimos, la excepción ("1") no se omite, pero se captura con éxito
Black Maggie
@ Black Maggie Se almacena en caché y se lanza una nueva excepción => esto no se almacena en caché y el programa finaliza. Y antes de que este bloque finalmente se ejecute.
nazar_art