Java Language Creazione e lettura di stacktraces


Esempio

Quando viene creato un oggetto eccezione (cioè quando lo si è new ), il costruttore Throwable acquisisce informazioni sul contesto in cui è stata creata l'eccezione. In seguito, queste informazioni possono essere visualizzate sotto forma di stacktrace, che può essere utilizzato per aiutare a diagnosticare il problema che ha causato l'eccezione in primo luogo.

Stampa di uno stacktrace

Stampare uno stacktrace è semplicemente una questione di chiamare il metodo printStackTrace() . Per esempio:

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

Il metodo printStackTrace() senza argomenti verrà stampato sull'output standard dell'applicazione; cioè l'attuale System.out . Esistono anche printStackTrace(PrintStream) e printStackTrace(PrintWriter) che vengono stampati su uno Stream o su un Writer specificato.

Gli appunti:

  1. Lo stacktrace non include i dettagli dell'eccezione stessa. Puoi usare il metodo toString() per ottenere quei dettagli; per esempio

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. La stampa Stacktrace dovrebbe essere usata con parsimonia; see Pitfall - Stacktraces eccessivi o inappropriati . È spesso preferibile utilizzare un framework di registrazione e passare l'oggetto di eccezione da registrare.

Capire uno stacktrace

Considera il seguente semplice programma composto da due classi in due file. (Abbiamo mostrato i nomi dei file e i numeri di riga aggiunti a scopo illustrativo.)

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      }

Quando questi file sono compilati ed eseguiti, otterremo il seguente output.

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)

Leggiamo questa riga alla volta per capire cosa ci sta dicendo.

La riga n. 1 ci dice che il thread chiamato "main" è terminato a causa di un'eccezione non rilevata. Il nome completo dell'eccezione è java.lang.ArithmeticException e il messaggio di eccezione è "/ per zero".

Se cerchiamo i javadoc per questa eccezione, si dice:

Gettato quando si è verificata una condizione aritmetica eccezionale. Ad esempio, un intero "divide per zero" genera un'istanza di questa classe.

In effetti, il messaggio "/ per zero" indica chiaramente che la causa dell'eccezione è che alcuni codici hanno tentato di dividere qualcosa per zero. Ma cosa?

Le restanti 3 linee sono la traccia dello stack. Ogni linea rappresenta una chiamata al metodo (o al costruttore) nello stack delle chiamate e ognuna ci dice tre cose:

  • il nome della classe e del metodo che è stato eseguito,
  • il nome file del codice sorgente,
  • il numero di riga del codice sorgente dell'istruzione che si stava eseguendo

Queste linee di uno stacktrace sono elencate con il frame per la chiamata corrente in alto. Il frame superiore nell'esempio sopra riportato è nel metodo Test.bar e alla riga 9 del file Test.java. Questa è la seguente riga:

    return a / b;

Se guardiamo prima un paio di righe nel file dove b è inizializzato, è chiaro che b avrà il valore zero. Possiamo dire senza alcun dubbio che questa è la causa dell'eccezione.

Se dovessimo andare oltre, possiamo vedere dallo stacktrace che bar() stato chiamato da foo() alla riga 3 di Test.java, e che foo() stato a sua volta chiamato da Main.main() .

Nota: i nomi di classi e metodi nei frame di stack sono i nomi interni per le classi e i metodi. Dovrai riconoscere i seguenti casi insoliti:

  • Una classe nidificata o interiore avrà l'aspetto di "OuterClass $ InnerClass".
  • Una classe interna anonima avrà l'aspetto di "OuterClass $ 1", "OuterClass $ 2", eccetera.
  • Quando viene eseguito il codice in un costruttore, inizializzatore campo istanza o un blocco inizializzatore istanza, il nome del metodo sarà "".
  • Quando viene eseguito il codice di un inizializzatore di campo statico o di un blocco di inizializzazione statico, il nome del metodo sarà "".

(In alcune versioni di Java, il codice di formattazione dello stacktrace rileverà ed eliderà sequenze ripetute dello stackframe, come può accadere quando un'applicazione fallisce a causa della ricorsione eccessiva.)

Eccezione di concatenamento e stacker nidificati

Java SE 1.4

Il concatenamento di eccezioni si verifica quando un pezzo di codice cattura un'eccezione e quindi crea e ne genera uno nuovo, passando la prima eccezione come causa. Ecco un esempio:

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  }

Quando la classe sopra è compilata ed eseguita, otteniamo il seguente stacktrace:

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

Lo stacktrace inizia con il nome della classe, il metodo e lo stack di chiamate per l'eccezione che (in questo caso) ha causato l'arresto anomalo dell'applicazione. Questo è seguito da una riga "Causato da:" che segnala l'eccezione di cause . Vengono riportati il ​​nome della classe e il messaggio, seguiti dai frame dello stack dell'eccezione causa. La traccia termina con un "... N more" che indica che gli ultimi N frame sono gli stessi dell'eccezione precedente.

"Caused by:" è incluso solo nell'output quando la cause dell'eccezione primaria non è null ). Le eccezioni possono essere concatenate indefinitamente, e in tal caso lo stacktrace può avere più tracce "Causate da:".

Nota: il meccanismo di cause stato esposto solo nell'API Throwable in Java 1.4.0. Prima di ciò, il concatenamento delle eccezioni doveva essere implementato dall'applicazione utilizzando un campo di eccezioni personalizzato per rappresentare la causa e un metodo printStackTrace personalizzato.

Catturare uno stacktrace come una stringa

A volte, un'applicazione deve essere in grado di acquisire uno stacktrace come String Java, in modo che possa essere utilizzato per altri scopi. L'approccio generale per fare ciò è creare un OutputStream o un Writer temporaneo che scriva su un buffer in memoria e lo passi a printStackTrace(...) .

Le librerie Apache Commons e Guava forniscono metodi di utilità per acquisire uno stacktrace come stringa:

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

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

Se non è possibile utilizzare librerie di terze parti nella propria base di codice, utilizzare il seguente metodo per eseguire l'attività:

   /**
     * 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();
    }

Si noti che se si intende analizzare lo stacktrace, è più semplice utilizzare getStackTrace() e getCause() piuttosto che tentare di analizzare uno stacktrace.