wake-up-neo.com

Wie funktioniert Javas System.exit () mit try / catch / finally-Blöcken?

Ich bin mir der Kopfschmerzen bewusst, die mit der Rückkehr in try/catch/finally-Blöcken einhergehen - Fälle, in denen die Rückkehr in finally immer die Rückkehr für die Methode ist, auch wenn eine Rückkehr in einem try- oder catch-Block ausgeführt werden sollte.

Gilt das auch für System.exit ()? Wenn ich zum Beispiel einen Try-Block habe:

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

Wenn es keine Ausnahmen gibt, welches System.exit () wird aufgerufen? Wenn der Exit eine return-Anweisung wäre, würde die Zeile System.exit (1) immer (?) Aufgerufen. Ich bin mir jedoch nicht sicher, ob sich exit anders verhält als return.

Der Code ist im Extremfall sehr schwierig, wenn nicht sogar unmöglich zu reproduzieren, so dass ich keinen Komponententest schreiben kann. Ich werde heute später versuchen, ein Experiment durchzuführen, wenn ich ein paar freie Minuten habe, aber ich bin trotzdem neugierig, und vielleicht weiß jemand auf SO) die Antwort und kann sie vorher bereitstellen oder falls ich kein Experiment durchführen kann.

66
Thomas Owens

Nein. System.exit(0) kehrt nicht zurück und der finally-Block wird nicht ausgeführt.

System.exit(int) kann ein SecurityException werfen. In diesem Fall wird der finally-Block will ausgeführt. Und da derselbe Principal dieselbe Methode aus derselben Codebasis aufruft, wird wahrscheinlich beim zweiten Aufruf eine andere SecurityException geworfen.


Hier ist ein Beispiel für den zweiten Fall:

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

Einfache Tests, die auch catch einschließen, zeigen, dass, wenn system.exit(0) keine Sicherheitsausnahme auslöst, dies die zuletzt ausgeführte Anweisung ist (catch und finally nicht) überhaupt ausgeführt).

Wenn system.exit(0) eine Sicherheitsausnahme auslöst, werden die Anweisungen catch und finally ausgeführt. Wenn sowohl catch als auch finallysystem.exit() -Anweisungen enthalten, werden nur Anweisungen ausgeführt, die diesen system.exit() -Anweisungen vorangehen.

In beiden oben beschriebenen Fällen wird die aufgerufene Methode nicht zurückgegeben, wenn der Code try zu einer von einer anderen Methode aufgerufenen Methode gehört.

Weitere Details hier (persönlicher Blog).

8

Andere Antworten haben behandelt, wie die Blöcke catch und finally nicht ausgeführt werden, wenn System.exit Die JVM beendet, ohne ein SecurityException zu werfen, aber sie werden nicht angezeigt Was passiert in einem "try-with-resources" -Block mit den Ressourcen: Sind sie geschlossen?

Nach dem JLS, Abschnitt 14.20.3.2 :

Durch die Übersetzung wird die Ressourcenspezifikation in die try-Anweisung eingefügt. Auf diese Weise kann eine catch-Klausel einer erweiterten try-with-resources-Anweisung eine Ausnahme abfangen, die auf die automatische Initialisierung oder das automatische Schließen einer Ressource zurückzuführen ist.

Darüber hinaus wurden alle Ressourcen zum Zeitpunkt der Ausführung des finally-Blocks geschlossen (oder versucht, geschlossen zu werden), was der Absicht des finally-Schlüsselworts entspricht.

Das heißt, Ressourcen sind closed, bevor ein Block catch oder finally ausgeführt wird. Was ist, wenn sie closed sind, selbst wenn catch und finally nicht ausgeführt werden?

Hier ist ein Code, der demonstriert, dass die Ressourcen in einer "try-with-resources" -Anweisung ebenfalls nicht geschlossen sind.

Ich benutze eine einfache Unterklasse von BufferedReader, die vor dem Aufruf von super.close Eine Anweisung ausgibt.

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

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

Dann habe ich den Testfall des Aufrufs von System.exit In der try-with-resources-Anweisung eingerichtet.

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

Ausgabe:

Beim Versuch

Daher werden nicht nur die Blöcke catch und finally nicht ausgeführt, sondern eine "try-with-resources" -Anweisung erhält keine Chance, ihre Ressourcen zu close, wenn System.exit Ist erfolgreich.

6
rgettman

der Block wird schließlich ausgeführt, egal was passiert.

der einzige Fall, in dem der Block finally nicht ausgeführt wird, ist der Aufruf der Methode 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!!!");
}

Es wird kein finaler Block ausgeführt. Das Programm wird nach der Anweisung System.exit () beendet.

3
Chinmoy

Wenn Sie dieses Verhalten als problematisch betrachten und eine genaue Kontrolle über Ihre System.exit - Aufrufe benötigen, können Sie die System.exit-Funktionalität nur in Ihre eigene Logik einbinden. Wenn wir das tun, können wir endgültige Blöcke ausführen und Ressourcen als Teil unseres Exit-Flows schließen.

Ich überlege, ob ich den System.exit - Aufruf und die Funktionalität in meine eigene statische Methode einbinden soll. In meiner Implementierung von exit würde ich eine benutzerdefinierte Unterklasse von Throwable oder Error auslösen und einen benutzerdefinierten Ausnahmehandler für nicht erfasste Ausnahmen mit Thread.setDefaultUncaughtExceptionHandler Implementieren, um diese Ausnahme zu behandeln. So wird mein Code:

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

Diese Strategie hat den zusätzlichen "Vorteil", diese Logik testbar zu machen: Um zu testen, ob die Methode, die unseren "Hauptfluss" enthält, das System tatsächlich zum Beenden auffordert, müssen wir nur ein Throwable abfangen und den Schreibtyp bestätigen. Ein Test für unseren Business Logic Wrapper könnte beispielsweise so aussehen:

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

Der Hauptnachteil dieser Strategie besteht darin, dass auf diese Weise die Funktionalität aus dem Ausnahmefluss entfernt wird, was häufig unbeabsichtigte Konsequenzen hat. Das offensichtlichste ist in diesem Fall, dass an jeder Stelle, an der Sie try { ... } catch(Throwable ex){ /*doesnt rethrow*/ } geschrieben haben, ein Update durchgeführt werden muss. Bibliotheken mit benutzerdefinierten Ausführungskontexten müssen nachgerüstet werden, um diese Ausnahme zu verstehen.

Alles in allem scheint mir dies eine gute Strategie zu sein. Glaubt das noch jemand hier?

2
Groostav
  1. Befindet sich im folgenden Beispiel System.exit(0) vor der Exception-Zeile, wird das Programm normal beendet, sodass FINALLY nicht ausgeführt wird.

  2. Wenn die System.exix(0) die letzte Zeile des try-Blocks ist, haben wir hier 2 Szenarien

    • wenn eine Ausnahme vorliegt, wird der endgültige Block ausgeführt
    • wenn keine Ausnahme vorliegt, wird der Block finally nicht ausgeführt

.

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