¿Cómo funciona System.exit () de Java con los bloques try / catch / finally? [duplicar]
-
05-07-2019 - |
Pregunta
Esta pregunta ya tiene una respuesta aquí:
- ¿Un bloque finalmente siempre se ejecuta en Java? 46 respuestas
Soy consciente de los dolores de cabeza que implican el retorno en los bloques try / catch / finally - casos en los que el retorno en el finalmente siempre es el retorno del método, incluso si un 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 try:
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 fuera una declaración de retorno, 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 que el retorno.
El código se encuentra en un caso extremo que es muy difícil, si no imposible, de reproducir, por lo que no puedo escribir una prueba unitaria. Intentaré realizar un experimento más tarde hoy, si tengo unos minutos libres, pero tengo curiosidad de todos modos, y tal vez alguien en SO sepa la respuesta y pueda proporcionarla antes o en caso de que no pueda ejecutar un experimento.
Solución
No. System.exit (0)
no regresa, y el bloque finalmente no se ejecuta.
System.exit (int)
puede lanzar una SecurityException
. Si eso sucede, finalmente se ejecutará el bloque . Y dado que el mismo principal está llamando al mismo método desde la misma base de código, es probable que se arroje 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);
}
}
}
Otros consejos
Las pruebas simples que incluyen catch
también revelan que si system.exit (0)
no arroja una excepción de seguridad, será la última instrucción ejecutada ( catch
y finalmente
no se ejecutan en absoluto).
Si system.exit (0)
arroja una excepción de seguridad, se ejecutan las declaraciones catch
y finalmente
. Si tanto catch
como finalmente
contienen system.exit ()
, solo las declaraciones que preceden a estas system.exit ()
las declaraciones se ejecutan.
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 aquí (blog personal).
Otras respuestas han cubierto cómo los bloques catch
y finalmente
no se ejecutan si System.exit
sale de la JVM sin lanzar un
Según el JLS , Sección 14.20.3.2 :
El efecto de la traducción es poner la especificación del recurso "dentro" La declaración de prueba. Esto permite que una cláusula catch de una declaración extendida de prueba con recursos capture una excepción debido a la inicialización o cierre automático de cualquier recurso.
Además, todos los recursos se habrán cerrado (o se habrá intentado cerrar) para cuando se ejecute el último bloque, de acuerdo con la intención de la última palabra clave.
Es decir, los recursos estarán cerrados
d antes de que se ejecute un bloque catch
o finalmente
. ¿Qué pasa si están close
d de alguna manera, incluso si catch
y finalmente
no se ejecutan?
Aquí hay un código para demostrar que los recursos en un " try-with-resources " la declaración tampoco está cerrada.
Utilizo 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 no se ejecutan los bloques catch
y finalmente
, un " prueba con recursos " la declaración no tendrá la oportunidad de cerrar
sus recursos si System.exit
tiene éxito.
finalmente el bloque se ejecutará pase lo que pase ... incluso si el bloque de prueba arroja algún elemento arrojable (excepción o error) .....
solo el caso finalmente el bloque 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 se ejecutará finalmente el bloque. El programa finalizará después de la declaración System.exit ().
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, finalmente podremos ejecutar bloques y cerrar recursos como parte de nuestro flujo de salida.
Lo que estoy considerando hacer es envolver la llamada System.exit
& amp; funcionalidad en mi propio método estático. En mi implementación de exit
, arrojaría una subclase personalizada de Throwable
o Error
, e implementaría un controlador de excepción 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 agregado de hacer comprobable esta lógica: para probar que el método que contiene nuestro flujo principal en realidad solicita que el sistema salga, todo lo que tenemos que hacer es atrapar un elemento arrojable y afirmar que ese 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)
El principal inconveniente de esta estrategia es que es una forma de sacar funcionalidad del flujo de excepciones, que a menudo tiene consecuencias no deseadas. La más obvia, en este caso, es que en cualquier lugar donde haya escrito intente {...} catch (Throwable ex) {/ * no vuelve a lanzar * /}
tendrá que actualizarse. En el caso de las bibliotecas que tienen contextos de ejecución personalizados, será necesario adaptarlas para comprender también esta excepción.
A fin de cuentas, esto me parece una buena estrategia. ¿Alguien más aquí piensa eso?
-
En el siguiente ejemplo, si
System.exit (0)
está antes de la línea de excepción, el programa finalizará normalmente, por lo que FINALMENTE no se ejecutará. -
Si el
System.exix (0)
es la última línea del bloque try, aquí tenemos 2 escenarios- cuando hay una excepción 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