Wyjątki w Javie, część 2: Zaawansowane funkcje i typy

JDK 1.0 wprowadził strukturę funkcji językowych i typów bibliotek do obsługi wyjątków , które są rozbieżnościami od oczekiwanego zachowania programu. W pierwszej połowie tego samouczka omówiono podstawowe możliwości obsługi wyjątków w języku Java. W drugiej połowie przedstawiono bardziej zaawansowane możliwości oferowane przez JDK 1.0 i jego następców: JDK 1.4, JDK 7 i JDK 9. Dowiedz się, jak przewidywać i zarządzać wyjątkami w programach Java przy użyciu zaawansowanych funkcji, takich jak śledzenie stosu, przyczyny i tworzenie łańcuchów wyjątków, wypróbuj -z zasobami, wielokrotnym łapaniem, ostatnim rzutem i chodzeniem po stosie.

Zwróć uwagę, że przykłady kodu w tym samouczku są zgodne z JDK 12.

pobierz Pobierz kod Pobierz kod źródłowy na przykład aplikacje w tym samouczku. Stworzone przez Jeffa Friesena dla JavaWorld.

Obsługa wyjątków w JDK 1.0 i 1.4: Ślady stosu

Każdy wątek JVM (ścieżka wykonywania) jest powiązany ze stosem, który jest tworzony podczas tworzenia wątku. Ta struktura danych jest podzielona na ramki , które są strukturami danych powiązanymi z wywołaniami metod. Z tego powodu stos każdego wątku jest często nazywany stosem wywołań metod .

Za każdym razem, gdy wywoływana jest metoda, tworzona jest nowa ramka. Każda ramka przechowuje zmienne lokalne, zmienne parametrów (które przechowują argumenty przekazane do metody), informacje o powrocie do metody wywołującej, miejsce do przechowywania wartości zwracanej, informacje przydatne przy wywoływaniu wyjątku i tak dalej.

Ślad stosu (znany również jako backtrace stosu ) jest raport aktywnych stos klatek w pewnym momencie, podczas wykonywania na gwint. ThrowableKlasa Java (w java.langpakiecie) zapewnia metody drukowania śladu stosu, wypełniania śladu stosu i uzyskiwania dostępu do elementów śladu stosu.

Drukowanie śladu stosu

Kiedy throwinstrukcja rzuca obiekt do rzucania, najpierw szuka odpowiedniego catchbloku w metodzie wykonawczej. Jeśli nie zostanie znaleziony, odwija ​​stos wywołań metody w poszukiwaniu najbliższego catchbloku, który może obsłużyć wyjątek. Jeśli nie zostanie znaleziony, maszyna JVM kończy się odpowiednim komunikatem. Rozważ listę 1.

Listing 1. PrintStackTraceDemo.java(wersja 1)

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

Wymyślony przykład z listy 1 tworzy java.io.IOExceptionobiekt i wyrzuca ten obiekt z main()metody. Ponieważ main()nie obsługuje tego elementu do rzucania, a ponieważ main()jest to metoda najwyższego poziomu, maszyna JVM kończy się odpowiednim komunikatem. W przypadku tej aplikacji zobaczysz następujący komunikat:

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

JVM wysyła ten komunikat przez wywołanie Throwable„s void printStackTrace()metodę, która drukuje ślad stosu dla wywołującego Throwableobiektu na standardowy strumień błędów. Pierwsza linia pokazuje wynik wywołania metody rzucającej toString(). W następnym wierszu wyświetlane są dane zarejestrowane wcześniej przez fillInStackTrace()(omówione w skrócie).

Dodatkowe metody śledzenia stosu wydruku

Throwablesą przeciążone, void printStackTrace(PrintStream ps)a void printStackTrace(PrintWriter pw)metody wyprowadzają ślad stosu do określonego strumienia lub modułu zapisującego.

Ślad stosu ujawnia plik źródłowy i numer wiersza, w którym utworzono przedmiot do rzucania. W tym przypadku został utworzony w linii 7 PrintStackTrace.javapliku źródłowego.

Możesz wywołać printStackTrace()bezpośrednio, zwykle z catchbloku. Weźmy na przykład pod uwagę drugą wersję PrintStackTraceDemoaplikacji.

Listing 2. PrintStackTraceDemo.java(wersja 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 ujawnia main()metodę, która wywołuje metodę a(), która wywołuje metodę b(). Sposób b()generuje IOExceptionobiektu do JVM, która odwija stos sposób, połączenia, aż znajdzie main()„S catchbloku, które może obsługiwać wyjątek. Wyjątek jest obsługiwany przez wywołanie printStackTrace()przedmiotu rzucanego. Ta metoda generuje następujące dane wyjściowe:

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

printStackTrace()nie wyświetla nazwy wątku. Zamiast tego wywołuje obiekt toString()rzucający, aby zwrócić w pełni kwalifikowaną nazwę klasy obiektu do rzucania ( java.io.IOException), która jest wypisywana w pierwszym wierszu. Następnie wyprowadza hierarchię wywołań metod: ostatnio wywołana metoda method ( b()) znajduje się na górze i main()na dole.

Jaką linię identyfikuje ślad stosu?

Ślad stosu identyfikuje linię, w której tworzony jest przedmiot do rzucania. Nie identyfikuje linii, na której rzucany jest rzut (przez throw), chyba że rzucany jest rzucany na tej samej linii, na której został utworzony.

Wypełnianie śladu stosu

Throwabledeklaruje Throwable fillInStackTrace()metodę, która wypełnia ślad stosu wykonywania. W Throwableobiekcie wywołującym zapisuje informacje o aktualnym stanie ramek stosu bieżącego wątku. Rozważ listę 3.

Listing 3. FillInStackTraceDemo.java(wersja 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(); } }

Główną różnicą między listą 3 a listą 2 jest instrukcja catchbloku throw (IOException) ioe.fillInStackTrace();. Ta instrukcja zastępuje ioeślad stosu, po którym obiekt rzutowany jest ponownie wyrzucany. Powinieneś obserwować ten wynik:

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)

Zamiast powtarzać początkowy ślad stosu, który identyfikuje lokalizację, w której IOExceptionobiekt został utworzony, drugi ślad stosu ujawnia lokalizację ioe.fillInStackTrace().

Konstruktorzy broni miotanej i fillInStackTrace()

ThrowableWywołuje każdy z konstruktorów fillInStackTrace(). Jednak następujący konstruktor (wprowadzony w JDK 7) nie wywoła tej metody po przejściu falsedo writableStackTrace:

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

fillInStackTrace()wywołuje metodę natywną, która przechodzi w dół stosu wywołań metody bieżącego wątku, aby utworzyć ślad stosu. Ten spacer jest kosztowny i może wpłynąć na wydajność, jeśli występuje zbyt często.

Jeśli napotkasz sytuację (na przykład dotyczącą urządzenia osadzonego), w której wydajność jest krytyczna, możesz zapobiec tworzeniu śladu stosu, zastępując go fillInStackTrace(). Sprawdź listę 4.

Listing 4. FillInStackTraceDemo.java(wersja 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

Wreszcie, JDK 1.4 wprowadził setStackTrace()metodę do Throwable. Ta metoda jest przeznaczona do użytku przez struktury zdalnego wywoływania procedur (RPC) i inne zaawansowane systemy, umożliwiając klientowi przesłonięcie domyślnego śladu stosu, który jest generowany fillInStackTrace()podczas konstruowania obiektu do rzucania.

Wcześniej pokazałem, jak przesłonić, fillInStackTrace()aby zapobiec tworzeniu śladu stosu. Zamiast tego można zainstalować nowy ślad stosu za pomocą StackTraceElementi setStackTrace(). Utwórz tablicę StackTraceElementobiektów zainicjowaną za pomocą następującego konstruktora i przekaż tę tablicę do setStackTrace():

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

Listing 6 przedstawia StackTraceElementi setStackTrace().