Ausnahmen in Java, Teil 2: Erweiterte Funktionen und Typen

Mit JDK 1.0 wurde ein Framework mit Sprachfunktionen und Bibliothekstypen für den Umgang mit Ausnahmen eingeführt , die vom erwarteten Programmverhalten abweichen. In der ersten Hälfte dieses Tutorials wurden die grundlegenden Funktionen zur Ausnahmebehandlung von Java behandelt. In dieser zweiten Hälfte werden erweiterte Funktionen von JDK 1.0 und seinen Nachfolgern vorgestellt: JDK 1.4, JDK 7 und JDK 9. Erfahren Sie, wie Sie Ausnahmen in Ihren Java-Programmen mithilfe erweiterter Funktionen wie Stack-Traces, Ursachen und Ausnahmeverkettung antizipieren und verwalten können -mit-Ressourcen, Multi-Catch, Final Re-Throw und Stack Walking.

Beachten Sie, dass Codebeispiele in diesem Lernprogramm mit JDK 12 kompatibel sind.

download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.

Ausnahmebehandlung in JDK 1.0 und 1.4: Stapelspuren

Jeder JVM- Thread (ein Ausführungspfad) ist einem Stapel zugeordnet , der beim Erstellen des Threads erstellt wird. Diese Datenstruktur ist in Frames unterteilt , bei denen es sich um Datenstrukturen handelt, die Methodenaufrufen zugeordnet sind. Aus diesem Grund wird der Stapel jedes Threads häufig als Methodenaufrufstapel bezeichnet .

Bei jedem Aufruf einer Methode wird ein neuer Frame erstellt. In jedem Frame werden lokale Variablen, Parametervariablen (die an die Methode übergebene Argumente enthalten), Informationen zum Zurückkehren zur aufrufenden Methode, Speicherplatz zum Speichern eines Rückgabewerts, Informationen zum Auslösen einer Ausnahme usw. gespeichert.

Ein Stack-Trace (auch als Stack-Backtrace bezeichnet ) ist ein Bericht über die aktiven Stack-Frames zu einem bestimmten Zeitpunkt während der Ausführung eines Threads. Die Java- ThrowableKlasse (im java.langPaket) bietet Methoden zum Drucken einer Stapelverfolgung, zum Ausfüllen einer Stapelverfolgung und zum Zugriff auf die Elemente einer Stapelverfolgung.

Drucken einer Stapelverfolgung

Wenn die throwAnweisung ein Throwable auslöst, sucht sie zuerst nach einem geeigneten catchBlock in der Ausführungsmethode. Wenn es nicht gefunden wird, wird der Methodenaufrufstapel abgewickelt, um nach dem nächsten catchBlock zu suchen, der die Ausnahme behandeln kann. Wenn nicht gefunden, wird die JVM mit einer geeigneten Nachricht beendet. Betrachten Sie Listing 1.

Listing 1. PrintStackTraceDemo.java(Version 1)

import java.io.IOException; public class PrintStackTraceDemo { public static void main(String[] args) throws IOException { throw new IOException(); } }

Das erfundene Beispiel von Listing 1 erstellt ein java.io.IOExceptionObjekt und wirft dieses Objekt aus der main()Methode. Da main()dies nicht möglich ist und weil main()es sich um die Methode der obersten Ebene handelt, wird die JVM mit einer geeigneten Nachricht beendet. Für diese Anwendung wird die folgende Meldung angezeigt:

Exception in thread "main" java.io.IOException at PrintStackTraceDemo.main(PrintStackTraceDemo.java:7)

Die JVM gibt diese Nachricht durch Aufrufen Throwableder void printStackTrace()Methode aus, die eine Stapelverfolgung für das aufrufende ThrowableObjekt im Standardfehlerstrom druckt . Die erste Zeile zeigt das Ergebnis des Aufrufs der Throwable- toString()Methode. Die nächste Zeile zeigt Daten, die zuvor von fillInStackTrace()(kurz besprochen) aufgezeichnet wurden .

Zusätzliche Druckstapel-Trace-Methoden

Throwable's überladen void printStackTrace(PrintStream ps)und void printStackTrace(PrintWriter pw)Methoden geben den Stack-Trace an den angegebenen Stream oder Writer aus.

Der Stack-Trace zeigt die Quelldatei und die Zeilennummer an, in der das Throwable erstellt wurde. In diesem Fall wurde es in Zeile 7 der PrintStackTrace.javaQuelldatei erstellt.

Sie können printStackTrace()direkt aufrufen , normalerweise von einem catchBlock aus. Betrachten Sie beispielsweise eine zweite Version der PrintStackTraceDemoAnwendung.

Listing 2. PrintStackTraceDemo.java(Version 2)

import java.io.IOException; public class PrintStackTraceDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { ioe.printStackTrace(); } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

Listing 2 zeigt eine main()Methode, die method a()aufruft und method aufruft b(). Verfahren b()wirft ein IOExceptionObjekt in die JVM, die den Stapel Methodenaufruf abwickelt , bis er feststellt , main()‚s - catchBlock, der die Ausnahme behandeln kann. Die Ausnahme wird durch Aufrufen printStackTrace()des Throwables behandelt. Diese Methode generiert die folgende Ausgabe:

java.io.IOException at PrintStackTraceDemo.b(PrintStackTraceDemo.java:24) at PrintStackTraceDemo.a(PrintStackTraceDemo.java:19) at PrintStackTraceDemo.main(PrintStackTraceDemo.java:9)

printStackTrace()gibt den Namen des Threads nicht aus. Stattdessen wird toString()das Throwable aufgerufen, um den vollständig qualifizierten Klassennamen ( java.io.IOException) des Throwables zurückzugeben , der in der ersten Zeile ausgegeben wird. Anschließend wird die Methodenaufrufhierarchie ausgegeben: Die zuletzt aufgerufene Methode ( b()) befindet sich oben und main()unten.

Welche Zeile identifiziert der Stack-Trace?

Die Stapelverfolgung gibt die Zeile an, in der ein Wurf erstellt wird. Es identifiziert nicht die Linie, in die der Wurf geworfen wird (via throw), es sei denn, der Wurf wird auf dieselbe Linie geworfen, in der er erstellt wurde.

Ausfüllen einer Stapelspur

Throwabledeklariert eine Throwable fillInStackTrace()Methode, die den Ausführungsstapel-Trace ausfüllt. Im aufrufenden ThrowableObjekt werden Informationen zum aktuellen Status der Stapelrahmen des aktuellen Threads aufgezeichnet. Betrachten Sie Listing 3.

Listing 3. FillInStackTraceDemo.java(Version 1)

import java.io.IOException; public class FillInStackTraceDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { ioe.printStackTrace(); System.out.println(); throw (IOException) ioe.fillInStackTrace(); } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

Der Hauptunterschied zwischen Listing 3 und Listing 2 ist die Anweisung des catchBlocks throw (IOException) ioe.fillInStackTrace();. Diese Anweisung ersetzt ioeden Stack-Trace, wonach das Throwable erneut ausgelöst wird. Sie sollten diese Ausgabe beachten:

java.io.IOException at FillInStackTraceDemo.b(FillInStackTraceDemo.java:26) at FillInStackTraceDemo.a(FillInStackTraceDemo.java:21) at FillInStackTraceDemo.main(FillInStackTraceDemo.java:9) Exception in thread "main" java.io.IOException at FillInStackTraceDemo.main(FillInStackTraceDemo.java:15)

Anstatt die anfängliche Stapelverfolgung zu wiederholen, die den Ort angibt, an dem das IOExceptionObjekt erstellt wurde, zeigt die zweite Stapelverfolgung den Ort von an ioe.fillInStackTrace().

Wurfkonstruktoren und fillInStackTrace()

Jeder ThrowableKonstruktor ruft auf fillInStackTrace(). Der folgende Konstruktor (eingeführt in JDK 7) ruft diese Methode jedoch nicht auf, wenn Sie falsean übergeben writableStackTrace:

Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)

fillInStackTrace()Ruft eine native Methode auf, die den Methodenaufrufstapel des aktuellen Threads durchläuft, um die Stapelverfolgung zu erstellen. Dieser Spaziergang ist teuer und kann die Leistung beeinträchtigen, wenn er zu oft auftritt.

Wenn Sie in eine Situation geraten (möglicherweise mit einem eingebetteten Gerät), in der die Leistung kritisch ist, können Sie verhindern, dass der Stack-Trace durch Überschreiben erstellt wird fillInStackTrace(). Check out Listing 4.

Listing 4. FillInStackTraceDemo.java(Version 2)

{ public static void main(String[] args) throws NoStackTraceException { try { a(); } catch (NoStackTraceException nste) { nste.printStackTrace(); } } static void a() throws NoStackTraceException { b(); } static void b() throws NoStackTraceException { throw new NoStackTraceException(); } } class NoStackTraceException extends Exception { @Override public synchronized Throwable fillInStackTrace() { return this; } }

Listing 4 introduces NoStackTraceException. This custom checked exception class overrides fillInStackTrace() to return this -- a reference to the invoking Throwable. This program generates the following output:

NoStackTraceException

Comment out the overriding fillInStackTrace() method and you'll observe the following output:

NoStackTraceException at FillInStackTraceDemo.b(FillInStackTraceDemo.java:22) at FillInStackTraceDemo.a(FillInStackTraceDemo.java:17) at FillInStackTraceDemo.main(FillInStackTraceDemo.java:7)

Accessing a stack trace's elements

At times you'll need to access a stack trace's elements in order to extract details required for logging, identifying the source of a resource leak, and other purposes. The printStackTrace() and fillInStackTrace() methods don't support this task, but JDK 1.4 introduced java.lang.StackTraceElement and its methods for this purpose.

The java.lang.StackTraceElement class describes an element representing a stack frame in a stack trace. Its methods can be used to return the fully-qualified name of the class containing the execution point represented by this stack trace element along with other useful information. Here are the main methods:

  • String getClassName() returns the fully-qualified name of the class containing the execution point represented by this stack trace element.
  • String getFileName() returns the name of the source file containing the execution point represented by this stack trace element.
  • int getLineNumber() returns the line number of the source line containing the execution point represented by this stack trace element.
  • String getMethodName() returns the name of the method containing the execution point represented by this stack trace element.
  • boolean isNativeMethod() returns true when the method containing the execution point represented by this stack trace element is a native method.

JDK 1.4 also introduced the StackTraceElement[] getStackTrace() method to the java.lang.Thread and Throwable classes. This method respectively returns an array of stack trace elements representing the invoking thread's stack dump and provides programmatic access to the stack trace information printed by printStackTrace().

Listing 5 demonstrates StackTraceElement and getStackTrace().

Listing 5. StackTraceElementDemo.java (version 1)

import java.io.IOException; public class StackTraceElementDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { StackTraceElement[] stackTrace = ioe.getStackTrace(); for (int i = 0; i < stackTrace.length; i++) { System.err.println("Exception thrown from " + stackTrace[i].getMethodName() + " in class " + stackTrace[i].getClassName() + " on line " + stackTrace[i].getLineNumber() + " of file " + stackTrace[i].getFileName()); System.err.println(); } } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

When you run this application, you'll observe the following output:

Exception thrown from b in class StackTraceElementDemo on line 33 of file StackTraceElementDemo.java Exception thrown from a in class StackTraceElementDemo on line 28 of file StackTraceElementDemo.java Exception thrown from main in class StackTraceElementDemo on line 9 of file StackTraceElementDemo.java

Schließlich führte JDK 1.4 die setStackTrace()Methode in ein Throwable. Diese Methode wurde für die Verwendung durch RPC-Frameworks (Remote Procedure Call) und andere erweiterte Systeme entwickelt, sodass der Client den Standard-Stack-Trace überschreiben kann, der fillInStackTrace()beim Erstellen eines Throwables generiert wird.

Ich habe zuvor gezeigt, wie man überschreibt fillInStackTrace(), um zu verhindern, dass ein Stack-Trace erstellt wird. Stattdessen können Sie mit StackTraceElementund einen neuen Stack-Trace installieren setStackTrace(). Erstellen Sie ein Array von StackTraceElementObjekten, die über den folgenden Konstruktor initialisiert wurden, und übergeben Sie dieses Array an setStackTrace():

StackTraceElement(String declaringClass, String methodName, String fileName, int lineNumber)

Listing 6 zeigt StackTraceElementund setStackTrace().