Java Language
Ausnahmen und Ausnahmebehandlung
Suche…
Einführung
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 demtry...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.
- Die Variable
- Ist dies nicht der Fall, setzt sich die ursprüngliche Ausnahme fort.
- Das Ausnahmeobjekt wird getestet, um zu sehen, ob es sich um eine Instanz von
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
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 derIllegalArgumentException
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
alsthrows IllegalArgumentException
. Dies war nicht unbedingt erforderlich, daIllegalArgumentException
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:
- UnsupportedOperationException - eine bestimmte Operation wird nicht unterstützt
- IllegalArgumentException - Ein ungültiger Parameterwert wurde an eine Methode übergeben
- IllegalStateException - Ihre API hat intern eine Bedingung erreicht, die niemals auftreten sollte oder die Folge einer ungültigen Verwendung Ihrer API ist
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
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
undServerSocket
und ihre Unterklassen -
Channel
und seine Unterklassen und - die JDBC-Schnittstellen
Connection
,Statement
undResultSet
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 blockiertfinally
. - 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:
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();
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
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:
- Die Unterscheidung zwischen geprüften und ungeprüften Ausnahmen wird im Folgenden beschrieben.
- Die
Throwable
,Exception
undRuntimeException
Klasse sollten alsabstract
behandelt werden. siehe Pitfall - Throwable, Exception, Error oder RuntimeException werfen . - 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. - Es wäre unklug, benutzerdefinierte Untertypen von
Throwable
zu deklarieren. Java-Tools und -Bibliotheken setzen möglicherweise voraus, dassError
undException
die einzigen direkten Subtypen vonThrowable
, undThrowable
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 derthrows
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:
- Beginnen Sie mit der Ausführung des Codes im
try
Block. - 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 vonArithmeticException
). - Wenn die Ausnahme abgefangen wird , wird sie der Variablen
e
zugewiesen und dercatch
Block wird ausgeführt. - Wenn entweder der
try
odercatch
Block abgeschlossen ist (dh während der Codeausführung keine nicht erfassten Ausnahmen auftreten), fahren Sie mit der Ausführung des Codes unterhalb destry-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
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:
Die in einem
StackTraceElement
verfügbaren Informationen sind begrenzt. Es sind keine weiteren Informationen verfügbar, als vonprintStackTrace
angezeigt werden. (Die Werte der lokalen Variablen im Frame sind nicht verfügbar.)Die Javadocs für
getStackTrace()
geben an, dass eine JVM Frames auslassengetStackTrace()
: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
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 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 Anweisungtry...finally
.
- Der Code im
- 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 im
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 demtry
Block, so dass sie für denfinally
Block gültig ist. - Mit dem
new FileReader(...)
kann dercatch
alleIOError
Ausnahmen behandeln, die beim Öffnen der Datei ausgelöst werden. - Wir benötigen ein
reader.close()
imfinally
Block, da es einige Ausnahmepfade gibt, die wir weder imtry
Block noch imcatch
Blockcatch
. - Da jedoch vor der Initialisierung des
reader
möglicherweise eine Ausnahme ausgelöst wurde, benötigen wir auch einen explizitennull
. - Schließlich kann der Aufruf von
reader.close()
(hypothetisch) eine Ausnahmereader.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 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:
Dem Compiler wird mitgeteilt, welche Ausnahmen ausgelöst werden, damit der Compiler nicht erfasste (geprüfte) Ausnahmen als Fehler melden kann.
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.