¿Cómo funciona Java''s System.exit() con los bloques try/catch/finally?
try-catch-finally (6)
Esta pregunta ya tiene una respuesta aquí:
- ¿Finalmente siempre se ejecuta en Java? 48 respuestas
Estoy al tanto de los dolores de cabeza que implican volver en los bloques try / catch / finally - casos en los que el retorno en el final siempre es el retorno del método, incluso si el retorno en un bloque try o catch debe ser el ejecutado.
Sin embargo, ¿se aplica lo mismo a System.exit ()? Por ejemplo, si tengo un bloque de prueba:
try {
//Code
System.exit(0)
}
catch (Exception ex) {
//Log the exception
}
finally {
System.exit(1)
}
Si no hay excepciones, ¿a qué System.exit () se llamará? Si la salida era una instrucción return, entonces la línea System.exit (1) siempre se llamaría (?). Sin embargo, no estoy seguro de si la salida se comporta de manera diferente a la devolución.
El código está en un caso extremo que es muy difícil, si no imposible, de reproducir, por lo que no puedo escribir una prueba unitaria. Voy a tratar de ejecutar un experimento más tarde hoy, si tengo unos minutos gratis, pero de todos modos tengo curiosidad, y tal vez alguien en SO sabe la respuesta y puede proporcionarla antes o en caso de que no pueda ejecutar un experimentar.
En el ejemplo siguiente, si
System.exit(0)
está antes de la línea de excepción, el programa terminará normalmente, por lo que FINALMENTE no se ejecutará.Si
System.exix(0)
es la última línea del bloque try, aquí tenemos 2 escenarios- cuando la excepción está presente, finalmente se ejecuta el bloque
- cuando la excepción no está presente, finalmente el bloque no se ejecuta
.
package com.exception;
public class UserDefind extends Exception {
private static int accno[] = {1001,1002,1003,1004,1005};
private static String name[] = {"raju","ramu","gopi","baby","bunny"};
private static double bal[] = {9000.00,5675.27,3000.00,1999.00,1600.00};
UserDefind(){}
UserDefind(String str){
super(str);
}
public static void main(String[] args) {
try {
//System.exit(0); -------------LINE 1---------------------------------
System.out.println("accno"+"/t"+"name"+"/t"+"balance");
for (int i = 0; i < 5; i++) {
System.out.println(accno[i]+"/t"+name[i]+"/t"+bal[i]);
//rise exception if balance < 2000
if (bal[i] < 200) {
UserDefind ue = new UserDefind("Balance amount Less");
throw ue;
}//end if
}//end for
//System.exit(0);-------------LINE 2---------------------------------
}//end try
catch (UserDefind ue)
{
System.out.println(ue);
}
finally{
System.out.println("Finnaly");
System.out.println("Finnaly");
System.out.println("Finnaly");
}
}//end of main
}//end of class
Finalmente, el bloque se ejecutará sin importar qué ... incluso si try block arroja cualquier throwable (excepción o error) .....
only case finally block no se ejecuta ... es cuando llamamos al método System.exit () ..
try{
System.out.println("I am in try block");
System.exit(1);
} catch(Exception ex){
ex.printStackTrace();
} finally {
System.out.println("I am in finally block!!!");
}
No ejecutará finalmente el bloqueo. El programa terminará después de la instrucción System.exit ().
Las pruebas simples, incluida la catch
también revelan que si system.exit(0)
no arroja una excepción de seguridad, será la última instrucción ejecutada ( catch
y finally
no se ejecutarán en absoluto).
Si system.exit(0)
arroja una excepción de seguridad, se ejecutan las sentencias catch
y finally
. Si ambos catch
y finally
contienen system.exit()
, solo se ejecutan las sentencias anteriores a estas system.exit()
.
En ambos casos descritos anteriormente, si el código try
pertenece a un método llamado por otro método, el método llamado no regresa.
Más detalles here (blog personal).
No. System.exit(0)
no regresa y el bloque finally no se ejecuta.
System.exit(int)
puede arrojar una SecurityException
. Si eso sucede, se ejecutará el bloque finally. Y dado que el mismo principal está llamando al mismo método desde la misma base de código, es probable que se genere otra SecurityException
desde la segunda llamada.
Aquí hay un ejemplo del segundo caso:
import java.security.Permission;
public class Main
{
public static void main(String... argv)
throws Exception
{
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm)
{
/* Allow everything else. */
}
@Override
public void checkExit(int status)
{
/* Don''t allow exit with any status code. */
throw new SecurityException();
}
});
System.err.println("I''m dying!");
try {
System.exit(0);
} finally {
System.err.println("I''m not dead yet!");
System.exit(1);
}
}
}
Otras respuestas han cubierto cómo los bloqueos catch
y finally
no se ejecutan si System.exit
sale de la JVM sin lanzar una SecurityException
, pero no muestran qué sucede en un bloque "try-with-resources" con los recursos: ¿Están ¿cerrado?
De acuerdo con el JLS, Sección 14.20.3.2 :
El efecto de la traducción es poner la especificación del recurso "dentro" de la declaración try. Esto permite que una cláusula catch de una instrucción try-with-resources extendida capture una excepción debido a la inicialización o cierre automático de cualquier recurso.
Además, todos los recursos se cerrarán (o intentarán cerrarse) cuando se ejecute el bloque finally, de acuerdo con la intención de la palabra clave finally.
Es decir, los recursos estarán close
d antes de que finally
ejecute un bloque catch
o finally
. ¿Qué pasa si están close
d de alguna manera, incluso si la catch
y, finally
, no se ejecuta?
Aquí hay un código para demostrar que los recursos en una declaración "try-with-resources" no están cerrados tampoco.
Uso una subclase simple de BufferedReader
que imprime una declaración antes de llamar a super.close
.
class TestBufferedReader extends BufferedReader {
public TestBufferedReader(Reader r) {
super(r);
}
@Override
public void close() throws IOException {
System.out.println("close!");
super.close();
}
}
Luego configuré el caso de prueba de llamar a System.exit
en la declaración try-with-resources.
public static void main(String[] args)
{
try (BufferedReader reader = new TestBufferedReader(new InputStreamReader(System.in)))
{
System.out.println("In try");
System.exit(0);
}
catch (Exception e)
{
System.out.println("Exception of type " + e.getClass().getName() + " caught: " + e.getMessage());
}
finally
{
System.out.println("finally!");
}
}
Salida:
En prueba
Por lo tanto, no solo catch
y finally
no se ejecutan bloques, una declaración de "prueba con recursos" no tendrá la oportunidad de close
sus recursos si System.exit
tiene éxito.
Si considera que este comportamiento es problemático y necesita un control preciso sobre sus llamadas a System.exit
, entonces lo único que puede hacer es ajustar la funcionalidad System.exit en su propia lógica. Si hacemos eso, podemos finalmente ejecutar bloques y obtener recursos cerrados como parte de nuestro flujo de salida.
Lo que estoy considerando hacer es System.exit
llamada y la funcionalidad System.exit
en mi propio método estático. En mi implementación de la exit
, lanzaría una subclase personalizada de Throwable
o Error
, e implementaría un controlador de excepciones Uncaught personalizado con Thread.setDefaultUncaughtExceptionHandler
para manejar esa excepción. Por lo tanto, mi código se convierte en:
//in initialization logic:
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
if(exception instanceof SystemExitEvent){
System.exit(((SystemExitEvent)exception).exitCode);
}
})
// in "main flow" or "close button" or whatever
public void mainFlow(){
try {
businessLogic();
Utilities.exit(0);
}
finally {
cleanUpFileSystemOrDatabaseConnectionOrWhatever();
}
}
//...
class Utilities {
// I''m not a fan of documentaiton,
// but this method could use it.
public void exit(int exitCode){
throw new SystemExitEvent(exitCode);
}
}
class SystemExitEvent extends Throwable {
private final int exitCode;
public SystemExitEvent(int exitCode){
super("system is shutting down")
this.exitCode = exitCode;
}
}
Esta estrategia tiene el "beneficio" adicional de hacer que esta lógica sea comprobable: para probar que el método que contiene nuestro "flujo principal" en realidad solicita al sistema que salga, todo lo que tenemos que hacer es capturar un objeto descartable y afirmar que es el tipo de escritura. Por ejemplo, una prueba para nuestro contenedor de lógica de negocios podría verse así:
//kotlin, a really nice language particularly for testing on the JVM!
@Test fun `when calling business logic should business the business`(){
//setup
val underTest = makeComponentUnderTest(configureToReturnExitCode = 42);
//act
val thrown: SystemExitEvent = try {
underTest.mainFlow();
fail("System Exit event not thrown!")
}
catch(event: SystemExitEvent){
event;
}
//assert
assertThat(thrown.exitCode).isEqualTo(42)
La principal desventaja de esta estrategia es que es una forma de obtener funcionalidad del flujo de excepciones, que a menudo tiene consecuencias imprevistas. El más obvio, en este caso, es que en cualquier lugar que hayas escrito, try { ... } catch(Throwable ex){ /*doesnt rethrow*/ }
tendrá que actualizarse. En el caso de las bibliotecas que tienen contextos de ejecución personalizados, deberán actualizarse para que también comprendan esta excepción.
En resumen, esto me parece una buena estrategia. ¿Alguien más aquí piensa eso?