Suche…


Einführung

Objekte vom Typ Throwable und deren Subtypen können mit dem Schlüsselwort throw auf den Stack geschickt und mit try…catch Anweisungen abgefangen werden.

Syntax

  • void someMethod () löst SomeException {} // Methodendeklaration aus und zwingt den Aufruf von Methodenaufrufen, wenn SomeException ein geprüfter Ausnahmetyp ist

  • Versuchen {

    someMethod(); //code that might throw an exception 
    

    }

  • catch (SomeException e) {

     System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
    

    }

  • endlich {

     //code that will always run, whether try block finishes or not
    

    }

Eine Ausnahme mit Try-Catch abfangen

Eine Ausnahme kann mit der try...catch Anweisung abgefangen und behandelt werden. (In der Tat nehmen try Anweisungen andere Formen an, wie in anderen Beispielen über try...catch...finally und try-with-resources .)

Try-catch mit einem catch-Block

Die einfachste Form sieht so aus:

try {
    doSomething();
} catch (SomeException e) {
    handle(e);
}
// next statement

Das Verhalten eines einfachen try...catch ist wie folgt:

  • Die Anweisungen im try Block werden ausgeführt.
  • Wenn von den Anweisungen im try Block keine Ausnahme ausgelöst wird, wird die Steuerung nach dem try...catch an die nächste Anweisung übergeben.
  • Wenn innerhalb des try Blocks eine Ausnahme ausgelöst wird.
    • Das Ausnahmeobjekt wird getestet, um zu sehen, ob es sich um eine Instanz von SomeException oder um einen Untertyp handelt.
    • Wenn ja, dann wird der catch wird Block die Ausnahme abfangen:
      • Die Variable e ist an das Ausnahmeobjekt gebunden.
      • Der Code innerhalb des catch Blocks wird ausgeführt.
      • Wenn dieser Code eine Ausnahme auslöst, wird die neu geworfene Ausnahme anstelle der ursprünglichen Ausnahme propagiert.
      • Ansonsten geht die Kontrolle nach dem try...catch zur nächsten Anweisung über.
    • Ist dies nicht der Fall, setzt sich die ursprüngliche Ausnahme fort.

Try-Catch mit mehreren Catches

Ein try...catch kann auch mehrere catch Blöcke haben. Zum Beispiel:

try {
    doSomething();
} catch (SomeException e) {
    handleOneWay(e)
} catch (SomeOtherException e) {
    handleAnotherWay(e);
}
// next statement

Wenn es mehrere catch Blöcke gibt, werden sie nacheinander beginnend mit dem ersten versucht, bis eine Übereinstimmung für die Ausnahme gefunden wird. Der entsprechende Handler wird ausgeführt (wie oben), und die Steuerung wird nach der try...catch Anweisung an die nächste Anweisung übergeben. Die catch Blöcke nach dem übereinstimmenden werden immer übersprungen, auch wenn der Handlercode eine Ausnahme auslöst .

Die Abgleichstrategie "von oben nach unten" hat Konsequenzen für Fälle, in denen die Ausnahmen in den catch nicht unzusammenhängend sind. Zum Beispiel:

try {
    throw new RuntimeException("test");
} catch (Exception e) {
    System.out.println("Exception");
} catch (RuntimeException e) {
    System.out.println("RuntimeException");
}

Dieses Code-Snippet gibt "Exception" statt "RuntimeException" aus. Da es sich bei RuntimeException um einen Untertyp von Exception , wird der erste (allgemeinere) catch abgeglichen. Der zweite (spezifischere) catch wird niemals ausgeführt.

Daraus können wir lernen, dass die spezifischsten catch (in Bezug auf die Ausnahmetypen) zuerst und die allgemeinsten als letzte angezeigt werden sollten. (Einige Java-Compiler werden Sie warnen, wenn ein catch niemals ausgeführt werden kann. Dies ist jedoch kein Kompilierungsfehler.)

Fangblöcke für mehrere Ausnahmen

Java SE 7

Beginnend mit Java SE 7 kann ein einzelner catch Block eine Liste nicht zusammenhängender Ausnahmen verarbeiten. Die Ausnahmetypen werden mit einem vertikalen Strich ( | ) angezeigt. Zum Beispiel:

try {
    doSomething();
} catch (SomeException | SomeOtherException e) {
    handleSomeException(e);
} 

Das Verhalten eines Ausnahmefalls mit mehreren Ausnahmen ist eine einfache Erweiterung für den Fall mit einer Ausnahme. Der catch stimmt überein, wenn die geworfene Ausnahme (mindestens) mit einer der aufgeführten Ausnahmen übereinstimmt.

Die Spezifikation enthält einige zusätzliche Feinheiten. Der Typ von e ist eine synthetische Vereinigung der Ausnahmetypen in der Liste. Wenn der Wert von e verwendet wird, ist sein statischer Typ der am wenigsten verbreitete Supertyp der Typvereinigung. Wenn jedoch e innerhalb des catch Blocks erneut ausgelöst wird, handelt es sich bei den ausgelösten Ausnahmetypen um die Typen in der Union. Zum Beispiel:

public void method() throws IOException, SQLException
    try {
        doSomething();
    } catch (IOException | SQLException e) {
        report(e);
        throw e;
    }

In der obigen SQLException werden IOException und SQLException auf Ausnahmen geprüft, deren am wenigsten üblicher Supertyp Exception . Dies bedeutet, dass die report mit dem report(Exception) übereinstimmen muss. Der Compiler weiß jedoch, dass der Wurf nur eine IOException oder eine SQLException throw kann. Daher kann die method als throws IOException, SQLException und nicht als throws Exception deklariert werden. (Was eine gute Sache ist: siehe Pitfall - Throwable Throwable, Exception, Error oder RuntimeException .)

Eine Ausnahme auslösen

Das folgende Beispiel zeigt die Grundlagen zum Auslösen einer Ausnahme:

public void checkNumber(int number) throws IllegalArgumentException {
    if (number < 0) {
        throw new IllegalArgumentException("Number must be positive: " + number);
    }
}

Die Ausnahme wird in der 3. Zeile geworfen. Diese Aussage kann in zwei Teile unterteilt werden:

  • new IllegalArgumentException(...) erstellt eine Instanz der IllegalArgumentException Klasse mit einer Nachricht, die den von der Ausnahme gemeldeten Fehler beschreibt.

  • throw ... wirft dann das Exception-Objekt.

Wenn die Ausnahme ausgelöst wird, werden die einschließenden Anweisungen abnormal beendet, bis die Ausnahme behandelt wird . Dies wird in anderen Beispielen beschrieben.

Es empfiehlt sich, das Ausnahmeobjekt in einer einzelnen Anweisung zu erstellen und zu werfen, wie oben gezeigt. Es ist außerdem empfehlenswert, eine aussagekräftige Fehlermeldung in die Ausnahme aufzunehmen, um dem Programmierer zu helfen, die Ursache des Problems zu verstehen. Dies ist jedoch nicht notwendigerweise die Nachricht, die Sie dem Endbenutzer zeigen sollten. (Java bietet zunächst keine direkte Unterstützung für die Internationalisierung von Ausnahmemeldungen.)

Es gibt noch ein paar weitere Punkte zu machen:

  • Wir haben die checkNumber als throws IllegalArgumentException . Dies war nicht unbedingt erforderlich, da IllegalArgumentException eine geprüfte Ausnahme ist. Siehe Die Java-Ausnahmehierarchie - Ungeprüfte und geprüfte Ausnahmen . Es ist jedoch empfehlenswert, dies zu tun und auch die Ausnahmen zu berücksichtigen, die die Javadoc-Kommentare einer Methode auslösen.

  • Code unmittelbar nach einer throw Anweisung ist nicht erreichbar . Also, wenn wir das geschrieben haben:

     throw new IllegalArgumentException("it is bad");
     return;
    

    Der Compiler meldet einen Kompilierungsfehler für die return .

Ausnahme-Verkettung

Viele Standardausnahmen haben einen Konstruktor mit einem zweiten cause zusätzlich zum herkömmlichen message . Die cause ermöglicht es Ihnen, Ausnahmen zu verketten. Hier ist ein Beispiel.

Zuerst definieren wir eine ungeprüfte Ausnahme, die von unserer Anwendung ausgelöst wird, wenn ein nicht behebbarer Fehler auftritt. Beachten Sie, dass wir einen Konstruktor eingefügt haben, der ein cause akzeptiert.

    public class AppErrorException extends RuntimeException {
        public AppErrorException() {
            super();
        }

        public AppErrorException(String message) {
            super(message);
        }

        public AppErrorException(String message, Throwable cause) {
            super(message, cause);
        }
    }

Nachfolgend finden Sie einen Code, der die Verkettung von Ausnahmen veranschaulicht.

    public String readFirstLine(String file) throws AppErrorException {
        try (Reader r = new BufferedReader(new FileReader(file))) {
            String line = r.readLine();
            if (line != null) {
                return line;
            } else {
                throw new AppErrorException("File is empty: " + file);
            }
        } catch (IOException ex) {
            throw new AppErrorException("Cannot read file: " + file, ex);
        }
    }

Der throw innerhalb des try Blocks erkennt ein Problem und meldet es über eine Ausnahme mit einer einfachen Nachricht. Im Gegensatz dazu behandelt der throw innerhalb des catch Blocks die IOException indem sie eine neue (geprüfte) Ausnahme IOException . Die ursprüngliche Ausnahme wird jedoch nicht weggeworfen. Wenn Sie die IOException als cause , zeichnen wir sie auf, damit sie im Stacktrace gedruckt werden kann, wie unter Erstellen und Lesen von Stacktraces erläutert.

Benutzerdefinierte Ausnahmen

In den meisten Fällen ist es aus Sicht des Code-Designs einfacher, vorhandene generische Exception Klassen zu verwenden, wenn Ausnahmen ausgelöst werden. Dies gilt insbesondere, wenn Sie nur die Ausnahme benötigen, um eine einfache Fehlermeldung zu erhalten. In diesem Fall wird normalerweise RuntimeException bevorzugt, da es sich nicht um eine geprüfte Ausnahme handelt. Es gibt andere Ausnahmeklassen für allgemeine Fehlerklassen:

Fälle, in denen Sie eine benutzerdefinierte Ausnahmeklasse verwenden möchten, umfassen Folgendes:

  • Sie schreiben eine API oder Bibliothek, die von anderen verwendet werden soll, und Sie möchten, dass Benutzer Ihrer API Ausnahmen spezifisch abfangen und behandeln können und diese Ausnahmen von anderen, allgemeineren Ausnahmen unterscheiden können .
  • Sie werfen Ausnahmen für eine bestimmte Art von Fehler in einem Teil Ihres Programms aus, die Sie in einem anderen Teil Ihres Programms abfangen und behandeln möchten, und Sie möchten diese Fehler von anderen, allgemeineren Fehlern unterscheiden können.

Sie können Ihre eigenen benutzerdefinierten Ausnahmen erstellen, indem Sie RuntimeException für eine ungeprüfte Ausnahme erweitern oder die Exception prüfen, indem Sie eine Exception die nicht auch eine Unterklasse von RuntimeException ist.

Unterklassen von Exception, die nicht ebenfalls Unterklassen von RuntimeException sind, sind geprüfte Ausnahmen

public class StringTooLongException extends RuntimeException {
    // Exceptions can have methods and fields like other classes
    // those can be useful to communicate information to pieces of code catching
    // such an exception
    public final String value;
    public final int maximumLength;

    public StringTooLongException(String value, int maximumLength){
        super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
        this.value = value;
        this.maximumLength = maximumLength;
    }
}

Diese können als vordefinierte Ausnahmen verwendet werden:

void validateString(String value){
    if (value.length() > 30){
        throw new StringTooLongException(value, 30);
    }
}

Die Felder können verwendet werden, wenn die Ausnahme abgefangen und behandelt wird:

void anotherMethod(String value){
    try {
        validateString(value);
    } catch(StringTooLongException e){
        System.out.println("The string '" + e.value + 
                "' was longer than the max of " + e.maximumLength );
    }
}

Beachten Sie, dass gemäß der Java-Dokumentation von Oracle :

[...] Wenn von einem Client vernünftigerweise erwartet wird, dass er sich von einer Ausnahme erholt, machen Sie ihn zu einer geprüften Ausnahme. Wenn ein Client nichts aus der Ausnahme wiederherstellen kann, machen Sie ihn zu einer ungeprüften Ausnahme.

Mehr:

Die try-with-resources-Anweisung

Java SE 7

Wie das Beispiel der try-catch-final-Anweisung veranschaulicht, ist für die Bereinigung von Ressourcen mit einer finally Klausel eine erhebliche Menge an "Boiler-Plate" -Code erforderlich, um die Randfälle korrekt zu implementieren. Java 7 bietet eine wesentlich einfachere Möglichkeit, dieses Problem in Form der try-with-resources- Anweisung zu lösen.

Was ist eine Ressource?

In Java 7 wurde die Schnittstelle java.lang.AutoCloseable eingeführt, mit der Klassen mit der try-with-resources- Anweisung verwaltet werden können. Instanzen von Klassen, die AutoCloseable implementieren, werden als Ressourcen bezeichnet . Diese müssen in der Regel rechtzeitig entsorgt werden, anstatt sich auf den Müllsammler zu verlassen.

Die AutoCloseable Schnittstelle definiert eine einzelne Methode:

public void close() throws Exception

Eine close() -Methode sollte die Ressource in geeigneter Weise entsorgen. Die Spezifikation besagt, dass es sicher sein sollte, die Methode für eine Ressource aufzurufen, die bereits entsorgt wurde. Darüber hinaus Klassen , die Umsetzung Autocloseable dringend empfohlen , den erklären ermutigt close() Methode zu werfen eine spezifischere Ausnahme als Exception oder keine Ausnahme überhaupt.

Eine Vielzahl von Java-Standardklassen und -Schnittstellen implementiert AutoCloseable . Diese schließen ein:

  • InputStream , OutputStream und ihre Unterklassen
  • Reader , Writer und ihre Unterklassen
  • Socket und ServerSocket und ihre Unterklassen
  • Channel und seine Unterklassen und
  • die JDBC-Schnittstellen Connection , Statement und ResultSet und ihre Unterklassen.

Anwendungen und Kurse von Drittanbietern können dies ebenfalls tun.

Die grundlegende try-with-resource-Anweisung

Die Syntax einer Try-With-Ressource basiert auf den klassischen Try-Catch- , Try-Final- und Try-Catch-Final- Formularen. Hier ist ein Beispiel für eine "Grundform"; dh die Form ohne catch oder finally .

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

Die zu verwaltenden Ressourcen werden im Abschnitt (...) nach der try Klausel als Variablen deklariert. In dem obigen Beispiel erklären wir eine Ressource variablen stream und initialisieren es zu einem neu erstellten PrintStream .

Nachdem die Ressourcenvariablen initialisiert wurden, wird der try Block ausgeführt. Nach Abschluss dieses stream.close() wird stream.close() automatisch aufgerufen, um sicherzustellen, dass die Ressource nicht ausläuft. Beachten Sie, dass der close() Aufruf unabhängig vom Abschluss des Blocks geschieht.

Die erweiterten try-with-resource-Anweisungen

Die try-with-resources- Anweisung kann wie bei der Try-catch-finally- Syntax vor Java 7 mit catch und finally Blöcken erweitert werden. Der folgende Codeausschnitt fügt unserem vorherigen einen catch Block hinzu, um die FileNotFoundException zu behandeln, die der PrintStream Konstruktor PrintStream kann:

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
} catch (FileNotFoundException ex) {
    System.err.println("Cannot open the file");
} finally {
    System.err.println("All done");
}

Wenn entweder die Ressourceninitialisierung oder der try-Block die Ausnahme catch , wird der catch Block ausgeführt. Der finally Block wird immer wie bei einer herkömmlichen try-catch-finally- Anweisung ausgeführt.

Es gibt jedoch ein paar Dinge zu beachten:

  • Die Ressourcenvariable befindet sich außerhalb des Bereichs in catch und blockiert finally .
  • Die Ressourcenbereinigung findet statt, bevor die Anweisung versucht, den catch Block abzugleichen.
  • Wenn die automatische Ressourcenbereinigung eine Ausnahme auslöst, könnte dies in einem der catch festgehalten werden.

Verwalten mehrerer Ressourcen

Die obigen Codeausschnitte zeigen eine einzige verwaltete Ressource. Try-with-resources kann sogar mehrere Ressourcen in einer Anweisung verwalten. Zum Beispiel:

try (InputStream is = new FileInputStream(file1);
     OutputStream os = new FileOutputStream(file2)) {
    // Copy 'is' to 'os'
}

Das verhält sich wie erwartet. Beide, is und os werden am Ende des try Blocks automatisch geschlossen. Es gibt einige Punkte zu beachten:

  • Die Initialisierungen erfolgen in der Codereihenfolge, und spätere Ressourcenvariablen-Initialisierer können die Werte der früheren verwenden.
  • Alle Ressourcenvariablen, die erfolgreich initialisiert wurden, werden bereinigt.
  • Ressourcenvariablen werden in umgekehrter Reihenfolge ihrer Deklarationen bereinigt.

In obigem Beispiel is also vor os initialisiert und danach bereinigt, und is wird bereinigt, wenn während der Initialisierung von os eine Ausnahme vorliegt.

Gleichwertigkeit von try-with-resource und klassischem try-catch-finally

Die Java-Sprachspezifikation gibt das Verhalten von Try-with-Resource- Formularen im Hinblick auf die klassische try-catch-finally- Anweisung an. (Weitere Informationen finden Sie in der JLS.)

Zum Beispiel diese grundlegende Try-with-Ressource :

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

ist als gleichwertig mit diesem try-catch-finally definiert :

// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");

// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;

// The actual try block
try {
    stream.println("Hello world!");
} catch (Throwable t) {
    // If an exception is thrown, remember it for the finally block
    primaryException = t;
    throw t;
} finally {
    if (primaryException == null) {
        // If no exception was thrown so far, exceptions thrown in close() will
        // not be caught and therefore be passed on to the enclosing code.
        stream.close();
    } else {
        // If an exception has already been thrown, any exception thrown in
        // close() will be suppressed as it is likely to be related to the
        // previous exception. The suppressed exception can be retrieved
        // using primaryException.getSuppressed().
        try {
            stream.close();
        } catch (Throwable suppressedException) {
            primaryException.addSuppressed(suppressedException);
        }
    }
}

(Das JLS gibt an, dass die tatsächlichen Variablen t und primaryException für normalen Java-Code nicht sichtbar sind.)

Die erweiterte Form der Try-With-Ressourcen wird als Äquivalenz mit der Basisform angegeben. Zum Beispiel:

try (PrintStream stream = new PrintStream(fileName)) {
    stream.println("Hello world!");
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}

ist äquivalent zu:

try {
    try (PrintStream stream = new PrintStream(fileName)) {
        stream.println("Hello world!");
    }
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}    

Stacktraces erstellen und lesen

Wenn ein Ausnahmeobjekt erstellt wird (dh, wenn Sie es new Throwable ), erfasst der Throwable Konstruktor Informationen zu dem Kontext, in dem die Ausnahme erstellt wurde. Diese Informationen können später in Form eines Stacktraces ausgegeben werden, mit dessen Hilfe das Problem diagnostiziert werden kann, das die Ausnahme in erster Linie verursacht hat.

Stacktrace drucken

Beim printStackTrace() eines Stacktraces muss lediglich die printStackTrace() -Methode printStackTrace() werden. Zum Beispiel:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

Die printStackTrace() -Methode ohne Argumente wird in der Standardausgabe der Anwendung gedruckt. dh das aktuelle System.out . Es gibt auch printStackTrace(PrintStream) und printStackTrace(PrintWriter) Überladungen, die in einem angegebenen Stream oder Writer drucken.

Anmerkungen:

  1. Der Stacktrace enthält nicht die Details der Ausnahme selbst. Sie können die toString() -Methode verwenden, um diese Details abzurufen. z.B

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. Stacktrace-Druck sollte sparsam verwendet werden; siehe Pitfall - Übermäßige oder unangemessene Stacktraces . Es ist häufig besser, ein Protokollierungsframework zu verwenden und das zu protokollierende Ausnahmeobjekt zu übergeben.

Stapelverfolgung verstehen

Betrachten Sie das folgende einfache Programm, das aus zwei Klassen in zwei Dateien besteht. (Wir haben die Dateinamen angezeigt und Zeilennummern zur Veranschaulichung hinzugefügt.)

File: "Main.java"
1   public class Main {
2       public static void main(String[] args) {
3           new Test().foo();
4       }
5   }

File: "Test.java"
1   class Test {
2       public void foo() {
3           bar();
4       }
5   
6       public int bar() {
7           int a = 1;
8           int b = 0;
9           return a / b;
10      }

Wenn diese Dateien kompiliert und ausgeführt werden, erhalten Sie die folgende Ausgabe.

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.bar(Test.java:9)
        at Test.foo(Test.java:3)
        at Main.main(Main.java:3)

Lassen Sie uns diese Zeile für Zeile lesen, um herauszufinden, was es uns sagt.

Zeile 1 sagt uns, dass der Thread "main" aufgrund einer nicht erfassten Ausnahme beendet wurde. Der vollständige Name der Ausnahme lautet java.lang.ArithmeticException , und die Ausnahmemeldung lautet "/ by zero".

Wenn wir die Javadocs für diese Ausnahme suchen, heißt es:

Wird ausgelöst, wenn eine außergewöhnliche arithmetische Bedingung vorliegt. Beispielsweise löst eine Ganzzahl "durch Null teilen" eine Instanz dieser Klasse aus.

Tatsächlich ist die Nachricht "/ by zero" ein deutlicher Hinweis darauf, dass die Ursache der Ausnahme darin liegt, dass ein Code versucht hat, etwas durch null zu teilen. Aber was?

Die restlichen 3 Zeilen sind die Stapelverfolgung. Jede Zeile stellt einen Methodenaufruf (oder Konstruktoraufruf) auf der Aufrufliste dar. In jeder Zeile werden drei Dinge angegeben:

  • Name der Klasse und Methode, die ausgeführt wurde,
  • der Quellcode-Dateiname,
  • Die Quellcode-Zeilennummer der Anweisung, die gerade ausgeführt wird

Diese Zeilen eines Stacktraces werden oben mit dem Frame für den aktuellen Anruf aufgelistet. Der obere Frame in unserem Beispiel oben befindet sich in der Test.bar Methode und in Zeile 9 der Test.java-Datei. Das ist die folgende Zeile:

    return a / b;

Wenn wir uns ein paar Zeilen früher in der Datei ansehen, wo b initialisiert wird, ist offensichtlich, dass b den Wert Null hat. Wir können ohne Zweifel sagen, dass dies die Ursache der Ausnahme ist.

Wenn wir weiter gehen mussten, können wir aus dem stacktrace ersehen, dass bar() von Zeile 3 von Test.java aus foo() aufgerufen wurde und dass foo() wiederum von Main.main() aufgerufen wurde.

Anmerkung: Die Klassen- und Methodennamen in den Stack-Frames sind die internen Namen für die Klassen und Methoden. Sie müssen die folgenden ungewöhnlichen Fälle erkennen:

  • Eine verschachtelte oder innere Klasse sieht wie "OuterClass $ InnerClass" aus.
  • Eine anonyme innere Klasse sieht wie "OuterClass $ 1", "OuterClass $ 2" usw. aus.
  • Wenn Code in einem Konstruktor, Instanzfeldinitialisierer oder einem Instanzinitialisiererblock ausgeführt wird, lautet der Methodenname "".
  • Wenn Code in einem statischen Feldinitialisierungs- oder statischen Initialisierungsblock ausgeführt wird, lautet der Methodenname "".

(In einigen Java-Versionen erkennt und entfernt der Stacktrace-Formatierungscode wiederholte Stackframe-Sequenzen, die auftreten können, wenn eine Anwendung aufgrund einer übermäßigen Rekursion ausfällt.)

Ausnahme-Verkettung und verschachtelte Stacktraces

Java SE 1.4

Die Verkettung von Ausnahmebedingungen geschieht, wenn ein Codeabschnitt eine Ausnahme abfängt und dann eine neue erstellt und auslöst, wobei die erste Ausnahme als Ursache übergeben wird. Hier ist ein Beispiel:

File: Test,java
1   public class Test {
2      int foo() {
3           return 0 / 0;
4      }
5
6       public Test() {
7           try {
8               foo();
9           } catch (ArithmeticException ex) {
10              throw new RuntimeException("A bad thing happened", ex);
11          }
12      }
13
14      public static void main(String[] args) {
15          new Test();
16      }
17  }

Wenn die obige Klasse kompiliert und ausgeführt wird, erhalten wir die folgende Stapelverfolgung:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
        at Test.<init>(Test.java:10)
        at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
        at Test.foo(Test.java:3)
        at Test.<init>(Test.java:8)
        ... 1 more

Der stacktrace beginnt mit dem Klassennamen, der Methode und dem Aufrufstack für die Ausnahme, die (in diesem Fall) zum Absturz der Anwendung geführt hat. Daran schließt sich eine Zeile "Caused by:" an, die die cause meldet. Der Klassenname und die Nachricht werden gemeldet, gefolgt von den Stack-Frames der Ursachenausnahme. Die Ablaufverfolgung endet mit einem "... N more", der angibt, dass die letzten N Frames mit denen der vorherigen Ausnahme identisch sind.

"Verursacht durch:" ist nur in der Ausgabe enthalten, wenn die cause der primären Ausnahme nicht null . Ausnahmen können unbegrenzt verkettet werden. In diesem Fall kann der Stacktrace mehrere "Verursacht durch:" - Spuren aufweisen.

Hinweis: Der cause wurde nur in der Throwable API in Java 1.4.0 verfügbar gemacht. Vorher musste die Verkettung von Ausnahmen von der Anwendung mithilfe eines benutzerdefinierten Ausnahmefelds zur Darstellung der Ursache und einer benutzerdefinierten printStackTrace Methode printStackTrace werden.

Erfassen eines Stacktraces als String

Manchmal muss eine Anwendung in der Lage sein, einen Stacktrace als Java- String zu erfassen, damit er für andere Zwecke verwendet werden kann. Im Allgemeinen wird dazu ein temporärer OutputStream oder Writer , der in einen In-Memory-Puffer schreibt und diesen an printStackTrace(...) .

Die Bibliotheken von Apache Commons und Guava bieten verschiedene Methoden zum Erfassen eines Stacktraces als String:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

Wenn Sie in Ihrer Codebasis keine Bibliotheken von Drittanbietern verwenden können, führen Sie die folgende Methode mit der Aufgabe aus:

   /**
     * Returns the string representation of the stack trace.
     *
     * @param throwable the throwable
     * @return the string.
     */
    public static String stackTraceToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

Wenn Sie den Stacktrace analysieren möchten, sollten Sie getStackTrace() und getCause() verwenden, getStackTrace() zu versuchen, einen Stacktrace zu analysieren.

Behandlung von InterruptedException

InterruptedException ist ein verwirrendes Tier - es erscheint in scheinbar harmlosen Methoden wie Thread.sleep() , aber die falsche Handhabung führt zu schwer zu verwaltendem Code, der sich in gleichzeitigen Umgebungen schlecht verhält.

Grundsätzlich bedeutet das, wenn eine InterruptedException aufgefangen wird, irgendjemand namens Thread.interrupt() in dem Thread, in dem Ihr Code gerade ausgeführt wird. Vielleicht Thread.interrupt() Sie sagen: "Es ist mein Code! Ich werde ihn niemals unterbrechen!" " und deshalb etwas so machen:

// Bad. Don't do this.
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // disregard
}

Dies ist jedoch genau der falsche Weg, um mit dem Auftreten eines "unmöglichen" Ereignisses umzugehen. Wenn Sie wissen, dass Ihre Anwendung niemals auf eine InterruptedException stößt, sollten Sie ein solches Ereignis als schwerwiegenden Verstoß gegen die Annahmen Ihres Programms behandeln und so schnell wie möglich beenden.

Die richtige Art, mit einem "unmöglichen" Interrupt umzugehen, ist wie folgt:

// When nothing will interrupt your code
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  throw new AssertionError(e);
}

Das macht zwei Dinge; Zunächst wird der Interrupt-Status des Threads wiederhergestellt (als ob die InterruptedException nicht an erster Stelle ausgelöst worden wäre). Anschließend wird ein AssertionError ausgelöst, der angibt, dass die grundlegenden Invarianten Ihrer Anwendung verletzt wurden. Wenn Sie sicher sind, dass Sie den Thread niemals unterbrechen, ist dieser Code sicher, da der catch Block niemals erreicht werden sollte.

Guava der Verwendung von Uninterruptibles Klasse hilft , dieses Muster zu vereinfachen; Das Aufrufen von Uninterruptibles.sleepUninterruptibly() ignoriert den unterbrochenen Status eines Threads, bis die Schlafdauer abgelaufen ist (an diesem Punkt wird er für spätere Aufrufe wiederhergestellt, um die eigene InterruptedException ). Wenn Sie wissen, dass Sie einen solchen Code niemals unterbrechen, kann auf diese Weise vermieden werden, dass Sie Ihre Schlafanrufe in einen Try-Catch-Block packen müssen.

Häufig können Sie jedoch nicht garantieren, dass Ihr Thread niemals unterbrochen wird. Insbesondere wenn Sie Code schreiben, der von einem Executor oder einem anderen Thread-Management ausgeführt wird, ist es wichtig, dass Ihr Code umgehend auf Interrupts reagiert. Andernfalls wird die Anwendung blockieren oder sogar blockieren.

In solchen Fällen empfiehlt es sich in der Regel, der InterruptedException die Weitergabe des Aufrufstapels zu erlauben, indem eine throws InterruptedException für jede Methode der Reihe nach hinzugefügt wird. Dies mag kludgy erscheinen, ist aber eigentlich eine wünschenswerte Eigenschaft - die Signaturen Ihrer Methode zeigen den Anrufern jetzt an, dass sie auf Interrupts umgehend antworten wird.

// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
  ...
}

In einigen wenigen Fällen (zB beim Überschreiben einer Methode, die nicht throw alle Ausnahmen geprüft) können Sie den unterbrochenen Status zurücksetzen , ohne Auslösen einer Ausnahme, in der Erwartung , was Code wird als nächstes ausgeführt , um die Unterbrechung zu behandeln. Dies verzögert die Behandlung der Unterbrechung, unterdrückt sie jedoch nicht vollständig.

// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  return ...; // your expectations are still broken at this point - try not to do more work.
}

Die Java-Ausnahmehierarchie - Ungeprüfte und geprüfte Ausnahmen

Alle Java-Ausnahmen sind Instanzen von Klassen in der Exception-Klassenhierarchie. Dies kann wie folgt dargestellt werden:

  • java.lang.Throwable - Dies ist die Basisklasse für alle Ausnahmeklassen. Seine Methoden und Konstruktoren implementieren eine Reihe von Funktionen, die allen Ausnahmen gemeinsam sind.
    • java.lang.Exception - Dies ist die Oberklasse aller normalen Ausnahmen.
      • verschiedene Standard- und benutzerdefinierte Ausnahmeklassen.
      • java.lang.RuntimeException - Dies ist die Superklasse aller normalen Ausnahmen, bei denen es sich um nicht geprüfte Ausnahmen handelt .
        • verschiedene Standard- und benutzerdefinierte Laufzeitausnahmeklassen.
    • java.lang.Error - Dies ist die Oberklasse aller "schwerwiegenden Fehlerausnahmen".

Anmerkungen:

  1. Die Unterscheidung zwischen geprüften und ungeprüften Ausnahmen wird im Folgenden beschrieben.
  2. Die Throwable , Exception und RuntimeException Klasse sollten als abstract behandelt werden. siehe Pitfall - Throwable, Exception, Error oder RuntimeException werfen .
  3. Die Error werden von der JVM in Situationen ausgelöst, in denen es für eine Anwendung unsicher oder unklug wäre, eine Wiederherstellung durchzuführen.
  4. Es wäre unklug, benutzerdefinierte Untertypen von Throwable zu deklarieren. Java-Tools und -Bibliotheken setzen möglicherweise voraus, dass Error und Exception die einzigen direkten Subtypen von Throwable , und Throwable falsch, wenn diese Annahme falsch ist.

Geprüfte versus nicht geprüfte Ausnahmen

In einigen Programmiersprachen wird kritisiert, dass Ausnahmen unterstützt werden. Es ist schwer zu wissen, welche Ausnahmen eine bestimmte Methode oder Prozedur auslösen kann. Da eine nicht behandelte Ausnahme zum Absturz eines Programms führen kann, können Ausnahmen zu einer Quelle der Fragilität werden.

Die Java-Sprache spricht dieses Problem mit dem geprüften Ausnahmemechanismus an. Erstens klassifiziert Java Ausnahmen in zwei Kategorien:

  • Geprüfte Ausnahmen stellen normalerweise erwartete Ereignisse dar, mit denen eine Anwendung umgehen kann. Beispielsweise repräsentieren IOException und ihre Subtypen Fehlerbedingungen, die bei E / A-Vorgängen auftreten können. Beispiele sind das Öffnen von Dateien, weil eine Datei oder ein Verzeichnis nicht vorhanden ist, das Lesen und Schreiben im Netzwerk schlägt fehl, weil eine Netzwerkverbindung unterbrochen wurde und so weiter.

  • Ungeprüfte Ausnahmen stellen normalerweise unerwartete Ereignisse dar, mit denen eine Anwendung nicht umgehen kann. Dies ist normalerweise das Ergebnis eines Fehlers in der Anwendung.

("Ausgelöst" bezieht sich im Folgenden auf jede Ausnahme, die explizit (durch eine throw Anweisung) oder implizit (bei einer fehlgeschlagenen Dereferenzierung, Typumwandlung usw.) ausgelöst wird. In ähnlicher Weise bezieht sich "propagiert" auf eine Ausnahme, die in einem verschachtelter Anruf und nicht innerhalb des Anrufs erfasst. Der folgende Beispielcode veranschaulicht dies.)

Der zweite Teil des geprüften Ausnahmemechanismus besteht darin, dass es Einschränkungen für Methoden gibt, bei denen eine geprüfte Ausnahme auftreten kann:

  • Wenn eine geprüfte Ausnahme in einer Methode ausgelöst oder propagiert wird, muss sie entweder von der Methode abgefangen oder in der throws Klausel der Methode aufgeführt werden. (Die Bedeutung der throws Klausel wird in diesem Beispiel beschrieben .)
  • Wenn eine geprüfte Ausnahme in einem Initialisierungsblock ausgelöst oder weitergegeben wird, muss der Block abgefangen werden.
  • Eine geprüfte Ausnahme kann nicht von einem Methodenaufruf in einem Feldinitialisierungsausdruck propagiert werden. (Eine solche Ausnahme kann nicht erkannt werden.)

Kurz gesagt, eine geprüfte Ausnahme muss entweder behandelt oder deklariert werden.

Diese Einschränkungen gelten nicht für ungeprüfte Ausnahmen. Dies gilt für alle Fälle, in denen eine Ausnahme implizit ausgelöst wird, da alle diese Fälle ungeprüfte Ausnahmen auslösen.

Überprüfte Ausnahmebeispiele

Diese Codeausschnitte sollen die geprüften Ausnahmebeschränkungen veranschaulichen. In jedem Fall zeigen wir eine Version des Codes mit einem Kompilierungsfehler und eine zweite Version mit dem korrigierten Fehler.

// This declares a custom checked exception.
public class MyException extends Exception {
    // constructors omitted.
}

// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
    // constructors omitted.
}

Das erste Beispiel zeigt, wie explizit geworfene geprüfte Ausnahmen als "geworfen" deklariert werden können, wenn sie nicht in der Methode behandelt werden sollen.

// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

Das zweite Beispiel zeigt, wie mit einer propagierten geprüften Ausnahme umgegangen werden kann.

// INCORRECT 
public void methodWithPropagatedCheckedException() {
    InputStream is = new FileInputStream("someFile.txt");  // Compilation error
    // FileInputStream throws IOException or a subclass if the file cannot 
    // be opened.  IOException is a checked exception.
    ...
}

// CORRECTED (Version A) 
public void methodWithPropagatedCheckedException() throws IOException {
    InputStream is = new FileInputStream("someFile.txt"); 
    ...
}

// CORRECTED (Version B) 
public void methodWithPropagatedCheckedException() {
    try {
        InputStream is = new FileInputStream("someFile.txt"); 
        ...
    } catch (IOException ex) {
        System.out.println("Cannot open file: " + ex.getMessage());
    }
}

Das letzte Beispiel zeigt, wie mit einer geprüften Ausnahmebedingung in einem statischen Feldinitialisierer umgegangen wird.

// INCORRECT
public class Test {
    private static final InputStream is = 
            new FileInputStream("someFile.txt");  // Compilation error
}

// CORRECTED
public class Test {
    private static final InputStream is;
    static {
        InputStream tmp = null;
        try {
            tmp = new FileInputStream("someFile.txt");
        } catch (IOException ex) {
            System.out.println("Cannot open file: " + ex.getMessage());
        }
        is = tmp;
    }
}

Beachten Sie, dass in diesem letzten Fall müssen wir uns auch mit den Problemen, die is nicht zugewiesen wurde, kann als einmal mehr sein, und doch hat auch zugewiesen werden, auch im Falle einer Ausnahme.

Einführung

Ausnahmen sind Fehler, die bei der Ausführung eines Programms auftreten. Betrachten Sie das Java-Programm, unter dem zwei Ganzzahlen geteilt werden.

class Division {
    public static void main(String[] args) {
 
        int a, b, result;
 
        Scanner input = new Scanner(System.in);
        System.out.println("Input two integers");
 
        a = input.nextInt();
        b = input.nextInt();
 
        result = a / b;
 
        System.out.println("Result = " + result);
    }
}

Jetzt kompilieren und führen wir den obigen Code aus und sehen die Ausgabe für eine versuchte Division durch Null:

Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero 
    at Division.main(Disivion.java:14)

Division durch Null ist eine ungültige Operation, die einen Wert erzeugen würde, der nicht als Ganzzahl dargestellt werden kann. Java behandelt dies, indem es eine Ausnahme auslöst . In diesem Fall handelt es sich bei der Ausnahme um eine Instanz der ArithmeticException- Klasse.

Hinweis: Das Beispiel zum Erstellen und Lesen von Stack-Traces erläutert, was die Ausgabe hinter den beiden Zahlen bedeutet.

Der Nutzen einer Ausnahme ist die Flusssteuerung, die sie erlaubt. Ohne Ausnahmen kann eine typische Lösung für dieses Problem sein, zuerst zu prüfen, ob b == 0 :

class Division {
    public static void main(String[] args) {
 
        int a, b, result;

        Scanner input = new Scanner(System.in);
        System.out.println("Input two integers");
 
        a = input.nextInt();
        b = input.nextInt();
 
        if (b == 0) {
            System.out.println("You cannot divide by zero.");
            return;
        }

        result = a / b;
 
        System.out.println("Result = " + result);
    }
}

Dies gibt die Nachricht aus, die You cannot divide by zero. auf die Konsole und beendet das Programm auf elegante Weise, wenn der Benutzer versucht, durch Null zu teilen. Eine äquivalente Möglichkeit, dieses Problem durch Ausnahmebehandlung zu lösen, besteht darin, die if Flusssteuerung durch einen try-catch Block zu ersetzen:

...

a = input.nextInt();
b = input.nextInt();
 
try {
    result = a / b;
}
catch (ArithmeticException e) {
    System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
    return;
}
 
...  

Ein try-catch-Block wird wie folgt ausgeführt:

  1. Beginnen Sie mit der Ausführung des Codes im try Block.
  2. Wenn im try-Block eine Ausnahme auftritt, brechen Sie sofort ab und prüfen Sie, ob diese Ausnahme vom catch Block abgefangen wird (in diesem Fall, wenn die Ausnahme eine Instanz von ArithmeticException ).
  3. Wenn die Ausnahme abgefangen wird , wird sie der Variablen e zugewiesen und der catch Block wird ausgeführt.
  4. Wenn entweder der try oder catch Block abgeschlossen ist (dh während der Codeausführung keine nicht erfassten Ausnahmen auftreten), fahren Sie mit der Ausführung des Codes unterhalb des try-catch Blocks fort.

Im Allgemeinen wird empfohlen, die Ausnahmebehandlung als Teil der normalen Ablaufsteuerung einer Anwendung zu verwenden, bei der das Verhalten andernfalls undefiniert oder unerwartet wäre. Zum Beispiel ist es in der Regel besser , eine null wenn eine Methode fehlschlägt, eine Ausnahme auszulösen, sodass die Anwendung, die die Methode verwendet, ihre eigene Flusssteuerung für die Situation über die Ausnahmebehandlung der oben dargestellten Art definieren kann. In gewissem Sinne wird dadurch das Problem umgangen, dass ein bestimmter Typ zurückgegeben werden muss , da eine von mehreren Arten von Ausnahmen ausgelöst werden kann, um auf das bestimmte aufgetretene Problem hinzuweisen.

Weitere Hinweise dazu, wie und wie Exceptions nicht verwendet werden, finden Sie unter Java-Fallstricke - Exceptions

Anweisungen in try catch block zurückgeben

Obwohl dies eine schlechte Praxis ist, können in einem Ausnahmebehandlungsblock mehrere return-Anweisungen hinzugefügt werden:

 public static int returnTest(int number){
    try{
        if(number%2 == 0) throw new Exception("Exception thrown");
        else return x;
    }
    catch(Exception e){
        return 3;
    }
    finally{
        return 7;
    }
}

Diese Methode gibt immer 7 zurück, da der finally-Block, der dem try / catch-Block zugeordnet ist, ausgeführt wird, bevor etwas zurückgegeben wird. Nun, da hat endlich return 7; Dieser Wert ersetzt die Try / Catch-Rückgabewerte.

Wenn der catch-Block einen primitiven Wert zurückgibt und dieser primitive Wert anschließend im finally-Block geändert wird, wird der im catch-Block zurückgegebene Wert zurückgegeben und die Änderungen aus dem finally-Block werden ignoriert.

Im folgenden Beispiel wird "0" und nicht "1" gedruckt.

public class FinallyExample {

    public static void main(String[] args) {
        int n = returnTest(4);
        
        System.out.println(n);
    }

    public static int returnTest(int number) {
        
        int returnNumber = 0; 
        
        try {
            if (number % 2 == 0)
                throw new Exception("Exception thrown");
            else
                return returnNumber;
        } catch (Exception e) {
            return returnNumber;
        } finally {
            returnNumber = 1;
        }
    }
}

Erweiterte Funktionen von Exceptions

In diesem Beispiel werden einige erweiterte Funktionen und Anwendungsfälle für Ausnahmen behandelt.

Den Callstack programmgesteuert untersuchen

Java SE 1.4

Mit Ausnahme von Stacktraces werden in erster Linie Informationen zu einem Anwendungsfehler und dessen Kontext bereitgestellt, sodass der Programmierer das Problem diagnostizieren und beheben kann. Manchmal kann es für andere Dinge verwendet werden. Beispielsweise muss eine SecurityManager Klasse möglicherweise den Aufrufstapel untersuchen, um zu entscheiden, ob der Code, der einen Aufruf ausführt, vertrauenswürdig ist.

Sie können Ausnahmen verwenden, um den Aufrufstapel programmatisch wie folgt zu untersuchen:

    Exception ex = new Exception();   // this captures the call stack
    StackTraceElement[] frames = ex.getStackTrace();
    System.out.println("This method is " + frames[0].getMethodName());
    System.out.println("Called from method " + frames[1].getMethodName());

Hier gibt es einige wichtige Einschränkungen:

  1. Die in einem StackTraceElement verfügbaren Informationen sind begrenzt. Es sind keine weiteren Informationen verfügbar, als von printStackTrace angezeigt werden. (Die Werte der lokalen Variablen im Frame sind nicht verfügbar.)

  2. Die Javadocs für getStackTrace() geben an, dass eine JVM Frames auslassen getStackTrace() :

    Einige virtuelle Maschinen können unter Umständen einen oder mehrere Stack-Frames aus der Stack-Ablaufverfolgung auslassen. Im Extremfall darf eine virtuelle Maschine, die über keine Stack-Trace-Informationen zu diesem auslösbaren Objekt verfügt, ein Array mit der Länge Null aus dieser Methode zurückgeben.

Ausnahme-Konstruktion optimieren

Wie bereits an anderer Stelle erwähnt, ist das Erstellen einer Ausnahme ziemlich teuer, da dazu Informationen über alle Stack-Frames im aktuellen Thread erfasst und aufgezeichnet werden müssen. Manchmal wissen wir, dass diese Informationen niemals für eine bestimmte Ausnahme verwendet werden. Beispielsweise wird der Stacktrace niemals gedruckt. In diesem Fall gibt es einen Implementierungstrick, den wir in einer benutzerdefinierten Ausnahme verwenden können, um zu bewirken, dass die Informationen nicht erfasst werden.

Die für Stacktraces erforderlichen Stack-Frame-Informationen werden erfasst, wenn die Throwable Konstruktoren die Throwable.fillInStackTrace() -Methode Throwable.fillInStackTrace() . Diese Methode ist public , was bedeutet, dass eine Unterklasse sie überschreiben kann. Der Trick besteht darin, die von Throwable vererbte Throwable mit einer Methode zu überschreiben, die nichts tut. z.B

  public class MyException extends Exception {
      // constructors

      @Override 
      public void fillInStackTrace() {
          // do nothing
      }
  }

Das Problem bei diesem Ansatz ist, dass eine Ausnahme, die fillInStackTrace() überschreibt, niemals den Stacktrace erfassen kann und in Szenarien, in denen Sie eine benötigen, nutzlos ist.

Löschen oder Ersetzen des Stacktraces

Java SE 1.4

In einigen Situationen enthält das Stacktrace für eine auf normale Weise erstellte Ausnahme entweder falsche Informationen oder Informationen, die der Entwickler dem Benutzer nicht preisgeben möchte. In diesen Szenarien kann Throwable.setStackTrace verwendet werden, um das Array von StackTraceElement Objekten zu ersetzen, das die Informationen enthält.

Zum Beispiel kann Folgendes verwendet werden, um die Stack-Informationen einer Ausnahme zu verwerfen:

 exception.setStackTrace(new StackTraceElement[0]);

Unterdrückte Ausnahmen

Java SE 7

Java 7 führte das try-with-resources- Konstrukt und das damit verbundene Konzept der Ausnahmeunterdrückung ein. Betrachten Sie den folgenden Ausschnitt:

try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
    // do stuff
    int temp = 0 / 0;    // throws an ArithmeticException
}

Wenn die Ausnahme ausgelöst wird, ruft der try close() auf dem w wodurch alle gepufferten Ausgaben FileWriter und der FileWriter . Aber was passiert, wenn eine IOException ausgelöst wird, während die Ausgabe IOException wird?

Was passiert, ist, dass jede Ausnahme, die beim Bereinigen einer Ressource ausgelöst wird, unterdrückt wird . Die Ausnahme wird abgefangen und der Liste der unterdrückten Ausnahmen der primären Ausnahme hinzugefügt. Als Nächstes werden die Try-with-Ressourcen mit der Bereinigung der anderen Ressourcen fortgesetzt. Schließlich wird die primäre Ausnahme erneut ausgegeben.

Ein ähnliches Muster tritt auf, wenn eine Ausnahme während der Ressourceninitialisierung ausgelöst wurde oder der try Block normal abgeschlossen wurde. Die erste geworfene Ausnahme wird zur primären Ausnahme und nachfolgende, die aus der Bereinigung resultieren, werden unterdrückt.

Die unterdrückten Ausnahmen können vom aufrufenden getSuppressedExceptions aus dem primären Ausnahmenobjekt getSuppressedExceptions .

Die Anweisungen try-finally und try-catch-finally

Die Anweisung try...catch...finally kombiniert die Ausnahmebehandlung mit Bereinigungscode. Der finally Block enthält Code, der unter allen Umständen ausgeführt wird. Dadurch sind sie für das Ressourcenmanagement und andere Arten der Bereinigung geeignet.

Versuchen Sie es endlich

Hier ist ein Beispiel für die einfachere ( try...finally ) Form:

try {
    doSomething();  
} finally {
    cleanUp();
}

Das Verhalten des try...finally wie folgt:

  • Der Code im try Block wird ausgeführt.
  • Wenn im try Block keine Ausnahme ausgelöst wurde:
    • Der Code im finally Block wird ausgeführt.
    • Wenn der finally Block eine Ausnahme auslöst, wird diese Ausnahme weitergegeben.
    • Ansonsten geht die Kontrolle nach dem try...finally Befehl auf die nächste Anweisung try...finally .
  • Wenn im try-Block eine Ausnahme ausgelöst wurde:
    • Der Code im finally Block wird ausgeführt.
    • Wenn der finally Block eine Ausnahme auslöst, wird diese Ausnahme weitergegeben.
    • Andernfalls setzt sich die ursprüngliche Ausnahme fort.

Der Code innerhalb finally Blocks wird immer ausgeführt. (Die einzigen Ausnahmen sind, wenn System.exit(int) aufgerufen wird, oder wenn die JVM in Panik gerät.) Daher ist ein finally Block der richtige System.exit(int) , der immer ausgeführt werden muss. zB das Schließen von Dateien und anderen Ressourcen oder das Aufheben von Sperren.

Try-Catch-Endlich

Unser zweites Beispiel zeigt, wie catch und finally verwendet werden können. Es zeigt auch, dass das Aufräumen von Ressourcen nicht einfach ist.

// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
    reader = new BufferedReader(new FileReader(fileName));
    result = reader.readLine();
} catch (IOException ex) {
    Logger.getLogger.warn("Unexpected IO error", ex);  // logging the exception
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException ex) {
            // ignore / discard this exception
        }
    }
}

Die vollständigen (hypothetischen) Verhaltensweisen von try...catch...finally in diesem Beispiel sind zu kompliziert, um sie hier zu beschreiben. Die einfache Version ist, dass der Code im finally Block immer ausgeführt wird.

Betrachten wir dies aus der Perspektive des Ressourcenmanagements:

  • Wir deklarieren die "Ressource" (dh reader ) vor dem try Block, so dass sie für den finally Block gültig ist.
  • Mit dem new FileReader(...) kann der catch alle IOError Ausnahmen behandeln, die beim Öffnen der Datei ausgelöst werden.
  • Wir benötigen ein reader.close() im finally Block, da es einige Ausnahmepfade gibt, die wir weder im try Block noch im catch Block catch .
  • Da jedoch vor der Initialisierung des reader möglicherweise eine Ausnahme ausgelöst wurde, benötigen wir auch einen expliziten null .
  • Schließlich kann der Aufruf von reader.close() (hypothetisch) eine Ausnahme reader.close() . Das interessiert uns nicht, aber wenn wir die Ausnahme nicht an der Quelle einfangen, müssen wir uns weiter oben im Aufrufstapel befassen.
Java SE 7

Java 7 und höher bieten eine alternative Try-with-Resources-Syntax, die die Ressourcenbereinigung erheblich vereinfacht.

Die 'throws'-Klausel in einer Methodendeklaration

Java geprüfte Ausnahme Mechanismus erfordert die Programmierer , dass bestimmte Methoden zu erklären specifed geprüfte Ausnahmen werfen konnte. Dies geschieht mit der throws Klausel. Zum Beispiel:

public class OddNumberException extends Exception { // a checked exception
}

public void checkEven(int number) throws OddNumberException {
    if (number % 2 != 0) {
        throw new OddNumberException();
    }
}

Das throws OddNumberException erklärt , dass ein Aufruf checkEven könnte eine Ausnahme auslösen , die vom Typ ist OddNumberException .

Eine throws Klausel kann eine Liste von Typen deklarieren und ungeprüfte Ausnahmen sowie geprüfte Ausnahmen enthalten.

public void checkEven(Double number) 
        throws OddNumberException, ArithmeticException {
    if (!Double.isFinite(number)) {
        throw new ArithmeticException("INF or NaN");
    } else if (number % 2 != 0) {
        throw new OddNumberException();
    }
}

Was ist der Sinn, ungeprüfte Ausnahmen als geworfen zu erklären?

Die throws Klausel in einer Methodendeklaration dient zwei Zwecken:

  1. Dem Compiler wird mitgeteilt, welche Ausnahmen ausgelöst werden, damit der Compiler nicht erfasste (geprüfte) Ausnahmen als Fehler melden kann.

  2. Sie teilt einem Programmierer mit, der Code schreibt, der die Methode aufruft, welche Ausnahmen zu erwarten sind. Zu diesem Zweck ist es oft sinnvoll, ungeprüfte Ausnahmen in eine throws .

Hinweis: Die throws wird auch vom Javadoc-Tool beim Generieren der API-Dokumentation und von den typischen "Hover-Text" -Methode-Tipps der IDE verwendet.

Würfe und Überschreiben der Methode

Die throws Klausel bildet einen Teil der Signatur einer Methode zum Zwecke des Überschreibens von Methoden. Eine Überschreibungsmethode kann mit derselben Gruppe von aktivierten Ausnahmen wie mit der überschriebenen Methode oder mit einer Teilmenge deklariert werden. Die Überschreibungsmethode kann jedoch keine zusätzlichen geprüften Ausnahmen hinzufügen. Zum Beispiel:

@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
    ...

@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
    ...

class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}

@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses

@Override
public void checkEven(Double number) throws IOExcepion         // ERROR

Der Grund für diese Regel besteht darin, dass die Überschreibungsmethode die Typenersetzbarkeit beeinträchtigen könnte, wenn eine überschriebene Methode eine geprüfte Ausnahme auslösen kann, die die überschriebene Methode nicht auslösen konnte.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow