web-dev-qa-db-fra.com

Comment fonctionne System.exit () de Java avec les blocs try / catch / finally?

Je suis conscient des maux de tête qui impliquent le retour dans les blocs try/catch/finally - cas où le retour dans le finalement est toujours le retour de la méthode, même si un retour dans un bloc try ou catch doit être celui exécuté.

Cependant, la même chose s'applique-t-elle à System.exit ()? Par exemple, si j'ai un bloc try:

try {
    //Code
    System.exit(0)
}
catch (Exception ex) {
    //Log the exception
}
finally {
    System.exit(1)
}

S'il n'y a pas d'exceptions, quel System.exit () sera appelé? Si la sortie était une instruction de retour, la ligne System.exit (1) serait toujours (?) Appelée. Cependant, je ne suis pas sûr que la sortie se comporte différemment du retour.

Le code est dans un cas extrême qui est très difficile, voire impossible, à reproduire, donc je ne peux pas écrire un test unitaire. Je vais essayer de lancer une expérience plus tard dans la journée, si j'obtiens quelques minutes gratuites, mais je suis curieux de toute façon, et peut-être que quelqu'un sur SO connaît la réponse et peut la fournir avant ou au cas où je ne pourrais pas exécuter une expérience.

66
Thomas Owens

Non. System.exit(0) ne revient pas et le bloc finally n'est pas exécuté.

System.exit(int) peut lancer un SecurityException. Si cela se produit, le bloc finalement sera sera exécuté. Et puisque le même principal appelle la même méthode à partir de la même base de code, un autre SecurityException est susceptible d'être lancé à partir du deuxième appel.


Voici un exemple du deuxième cas:

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);
    }
  }

}
76
erickson

Des tests simples incluant catch révèlent également que si system.exit(0) ne lève pas d'exception de sécurité, ce sera la dernière instruction exécutée (catch et finally ne sont pas exécutés du tout).

Si system.exit(0) lève une exception de sécurité, les instructions catch et finally sont exécutées. Si catch et finally contiennent des instructions system.exit(), seules les instructions précédant ces instructions system.exit() sont exécutées.

Dans les deux cas décrits ci-dessus, si le code try appartient à une méthode appelée par une autre méthode, la méthode appelée ne retourne pas.

Plus de détails ici (blog personnel).

8
Jérôme Verstrynge

D'autres réponses ont expliqué comment les blocs catch et finally ne s'exécutent pas si System.exit quitte la JVM sans lancer un SecurityException, mais ils ne montrent pas ce qui se passe dans un bloc "try-with-resources" aux ressources: sont-elles fermées?

Selon le JLS, section 14.20.3.2 :

La traduction a pour effet de placer la spécification de ressource "à l'intérieur" de l'instruction try. Cela permet à une clause catch d'une instruction try-with-resources étendue d'intercepter une exception en raison de l'initialisation ou de la fermeture automatique de toute ressource.

De plus, toutes les ressources auront été fermées (ou tentées de l'être) au moment où le bloc finally est exécuté, conformément à l'intention du mot clé finally.

Autrement dit, les ressources seront closed avant l'exécution d'un bloc catch ou finally. Que faire s'ils sont closed d'une manière ou d'une autre même si catch et finally ne s'exécutent pas?

Voici du code pour démontrer que les ressources d'une instruction "try-with-resources" ne sont pas fermées non plus.

J'utilise une simple sous-classe de BufferedReader qui imprime une instruction avant d'appeler super.close.

class TestBufferedReader extends BufferedReader {
    public TestBufferedReader(Reader r) {
        super(r);
    }

    @Override
    public void close() throws IOException {
        System.out.println("close!");
        super.close();
    }
}

Ensuite, j'ai mis en place le cas de test d'appeler System.exit dans l'instruction 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!");
    }
}

Sortie:

En essai

Par conséquent, non seulement les blocs catch et finally ne s'exécutent pas, mais une instruction "essayer avec des ressources" n'aura pas la chance de close ses ressources si System.exit réussit.

6
rgettman

enfin le bloc sera exécuté quoi qu'il arrive .... même si le bloc try lance n'importe quel objet jetable (exception ou erreur) .....

le seul cas où finalement le bloc ne s'exécute pas ... c'est lorsque nous appelons la méthode 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!!!");
}

Il n'exécutera pas finalement le bloc. Le programme sera terminé après l'instruction System.exit ().

3
Chinmoy

Si vous considérez ce comportement problématique et que vous avez besoin d'un contrôle précis sur vos appels System.exit, La seule chose que vous pouvez faire est d'encapsuler la fonctionnalité System.exit dans votre propre logique. Si nous le faisons, nous pouvons enfin exécuter les blocs et fermer les ressources dans le cadre de notre flux de sortie.

Ce que j'envisage de faire, c'est d'encapsuler l'appel et la fonctionnalité System.exit Dans ma propre méthode statique. Dans mon implémentation de exit, je lancerais une sous-classe personnalisée de Throwable ou Error, et implémenterais un gestionnaire d'exceptions Uncaught personnalisé avec Thread.setDefaultUncaughtExceptionHandler Pour gérer cette exception. Ainsi mon code devient:

//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;
  }
} 

Cette stratégie a l'avantage supplémentaire de rendre cette logique testable: pour tester que la méthode contenant notre "flux principal" demande réellement au système de quitter, tout ce que nous avons à faire est d'attraper un objet jetable et d'affirmer qu'il s'agit du type d'écriture. Par exemple, un test pour notre wrapper de logique métier peut ressembler à:

//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)

L'inconvénient majeur de cette stratégie est qu'elle permet de tirer des fonctionnalités d'un flux d'exception, ce qui a souvent des conséquences inattendues. Le plus évident, dans ce cas, est que partout où vous avez écrit try { ... } catch(Throwable ex){ /*doesnt rethrow*/ } devra être mis à jour. Dans le cas des bibliothèques qui ont des contextes d'exécution personnalisés, elles devront être mises à niveau pour comprendre également cette exception.

Dans l'ensemble, cela me semble être une bonne stratégie. Quelqu'un d'autre ici le pense-t-il?

2
Groostav
  1. Dans l'exemple ci-dessous, si System.exit(0) est avant la ligne d'exception, le programme se terminera normalement, donc FINALLY ne s'exécutera pas.

  2. Si la System.exix(0) est la dernière ligne du bloc try, nous avons ici 2 scénarios

    • quand une exception est présente, le bloc est finalement exécuté
    • quand aucune exception n'est présente, le bloc n'est finalement pas exécuté

.

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
0
honey