Eccezioni in Java, Parte 2: caratteristiche e tipi avanzati

JDK 1.0 ha introdotto un framework di funzionalità del linguaggio e tipi di libreria per gestire le eccezioni , che sono divergenze dal comportamento previsto del programma. La prima metà di questo tutorial ha coperto le capacità di gestione delle eccezioni di base di Java. Questa seconda metà introduce funzionalità più avanzate fornite da JDK 1.0 e dai suoi successori: JDK 1.4, JDK 7 e JDK 9. Scopri come anticipare e gestire le eccezioni nei tuoi programmi Java utilizzando funzionalità avanzate come tracce dello stack, cause e concatenamento delle eccezioni, prova -con-resources, multi-catch, final re-throw e stack walking.

Nota che gli esempi di codice in questo tutorial sono compatibili con JDK 12.

download Ottieni il codice Scarica il codice sorgente per applicazioni di esempio in questo tutorial. Creato da Jeff Friesen per JavaWorld.

Gestione delle eccezioni in JDK 1.0 e 1.4: tracce dello stack

Ogni thread JVM (un percorso di esecuzione) è associato a uno stack che viene creato quando viene creato il thread. Questa struttura di dati è divisa in frame , che sono strutture di dati associate alle chiamate di metodo. Per questo motivo, lo stack di ogni thread viene spesso definito stack di chiamate al metodo .

Ogni volta che viene chiamato un metodo viene creato un nuovo frame. Ogni frame memorizza variabili locali, variabili di parametro (che contengono argomenti passati al metodo), informazioni per tornare al metodo chiamante, spazio per memorizzare un valore restituito, informazioni utili per inviare un'eccezione e così via.

Uno stack trace (noto anche come stack backtrace ) è un report degli stack frame attivi in ​​un determinato momento durante l'esecuzione di un thread. La Throwableclasse Java (nel java.langpacchetto) fornisce metodi per stampare una traccia dello stack, compilare una traccia dello stack e accedere agli elementi di una traccia dello stack.

Stampa di una traccia dello stack

Quando l' throwistruzione lancia un oggetto lanciabile, cerca prima un catchblocco adatto nel metodo di esecuzione. Se non viene trovato, svolge lo stack di chiamate al metodo cercando il catchblocco più vicino in grado di gestire l'eccezione. Se non viene trovata, la JVM termina con un messaggio appropriato. Considera il listato 1.

Listato 1. PrintStackTraceDemo.java(versione 1)

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

L'esempio inventato del listato 1 crea un java.io.IOExceptionoggetto e lo getta fuori dal main()metodo. Poiché main()non gestisce questo lancio, e poiché main()è il metodo di primo livello, la JVM termina con un messaggio appropriato. Per questa applicazione, vedrai il seguente messaggio:

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

La JVM emette questo messaggio chiamando Throwableil void printStackTrace()metodo di, che stampa una traccia dello stack per l' Throwableoggetto richiamante sul flusso di errore standard. La prima riga mostra il risultato dell'invocazione del toString()metodo throwable . La riga successiva mostra i dati precedentemente registrati da fillInStackTrace()(discussa a breve).

Metodi di tracciamento dello stack di stampa aggiuntivi

ThrowableI metodi di overload void printStackTrace(PrintStream ps)e void printStackTrace(PrintWriter pw)restituiscono la traccia dello stack al flusso o al writer specificato.

L'analisi dello stack rivela il file di origine e il numero di riga in cui è stato creato l'elemento lanciabile. In questo caso, è stato creato sulla riga 7 del PrintStackTrace.javafile sorgente.

Puoi invocare printStackTrace()direttamente, in genere da un catchblocco. Ad esempio, considera una seconda versione PrintStackTraceDemodell'applicazione.

Listato 2. PrintStackTraceDemo.java(versione 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(); } }

Il Listato 2 rivela un main()metodo che chiama metodo a(), che chiama metodo b(). Il metodo b()lancia un IOExceptionoggetto alla JVM, che svolge lo stack di chiamate al metodo finché non trova main()il catchblocco di, che può gestire l'eccezione. L'eccezione viene gestita invocando printStackTrace()sul throwable. Questo metodo genera il seguente output:

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

printStackTrace()non restituisce il nome del thread. Invece, invoca toString()il throwable per restituire il nome completo della classe del throwable ( java.io.IOException), che viene emesso sulla prima riga. Quindi restituisce la gerarchia delle chiamate al metodo: il metodo chiamato più di recente ( b()) è in alto e main()in basso.

Quale linea identifica la traccia dello stack?

La traccia dello stack identifica la riga in cui viene creato un throwable. Non identifica la linea in cui viene lanciato il lanciabile (tramite throw), a meno che il lanciabile non venga lanciato sulla stessa linea in cui è stato creato.

Compilando una traccia dello stack

Throwabledichiara un Throwable fillInStackTrace()metodo che riempie la traccia dello stack di esecuzione. ThrowableNell'oggetto invocante , registra le informazioni sullo stato corrente degli stack frame del thread corrente. Considera il listato 3.

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

La differenza principale tra il listato 3 e il listato 2 è l' istruzione catchdel blocco throw (IOException) ioe.fillInStackTrace();. Questa istruzione sostituisce ioela traccia dello stack di, dopo di che il throwable viene rilanciato. Dovresti osservare questo output:

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)

Invece di ripetere la traccia dello stack iniziale, che identifica la posizione in cui è IOExceptionstato creato l' oggetto, la seconda traccia dello stack rivela la posizione di ioe.fillInStackTrace().

Costruttori lanciabili e fillInStackTrace()

Ciascuno dei Throwablecostruttori di invoca fillInStackTrace(). Tuttavia, il seguente costruttore (introdotto in JDK 7) non richiamerà questo metodo quando si passa falsea writableStackTrace:

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

fillInStackTrace()richiama un metodo nativo che percorre lo stack di chiamate al metodo del thread corrente per creare l'analisi dello stack. Questa passeggiata è costosa e può influire sulle prestazioni se si verifica troppo spesso.

Se ti imbatti in una situazione (forse che coinvolge un dispositivo incorporato) in cui le prestazioni sono critiche, puoi impedire la creazione della traccia dello stack eseguendo l'override fillInStackTrace(). Dai un'occhiata al listato 4.

Listato 4. FillInStackTraceDemo.java(versione 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

Infine, JDK 1.4 ha introdotto il setStackTrace()metodo in Throwable. Questo metodo è progettato per essere utilizzato da framework RPC (Remote Procedure Call) e altri sistemi avanzati, consentendo al client di eseguire l'override fillInStackTrace()dell'analisi dello stack predefinita generata da quando viene creato un oggetto throwable.

In precedenza ho mostrato come eseguire l'override fillInStackTrace()per impedire la creazione di una traccia dello stack. È invece possibile installare una nuova traccia dello stack utilizzando StackTraceElemente setStackTrace(). Crea un array di StackTraceElementoggetti inizializzati tramite il seguente costruttore e passa questo array a setStackTrace():

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

Il listato 6 mostra StackTraceElemente setStackTrace().