Menu

Eccezioni in Java: errori, stack trace e throw

Cos'è un'eccezione in Java, come leggere uno stack trace, la differenza tra eccezioni checked e unchecked, la gerarchia delle eccezioni e come lanciare le tue.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Cos'è un'eccezione

Un'eccezione è il modo in cui Java dice "qualcosa è andato storto e non posso continuare normalmente". Invece di restituire un valore sbagliato o corrompere in silenzio i tuoi dati, il runtime crea un oggetto eccezione che descrive il problema e lo lancia. L'esecuzione normale si ferma in quel punto e Java inizia a cercare del codice che sappia gestire la situazione.

Se nessuno la gestisce, l'eccezione arriva in cima al programma, la JVM stampa uno stack trace e il processo termina con uno stato diverso da zero.

Nota che "after" non viene mai stampato. Nell'istante in cui viene valutato numbers[5], viene lanciata una ArrayIndexOutOfBoundsException e il resto di main viene abbandonato: andare oltre l'ultimo indice valido di un array è uno dei modi più comuni per provocare un'eccezione.

Leggere uno stack trace

Quando un'eccezione non viene gestita, ottieni un output come questo. Sembra minaccioso, ma è solo un elenco:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Main.divide(Main.java:8)
	at Main.main(Main.java:4)

Leggilo dall'alto in basso:

  • La prima riga è il tipo (ArithmeticException) e un messaggio (/ by zero).
  • Ogni riga at ... è uno stack frame. Quello più in alto è il punto in cui l'eccezione è stata davvero lanciata: divide alla riga 8. Sotto c'è il chiamante, main alla riga 4.

La riga at più in alto è quella da guardare per prima. I frame sottostanti rispondono alla domanda "come siamo arrivati qui?"

Eseguilo e lo stack trace punta dritto alla riga a / b, poi mostra main come chiamante. Questa catena di chiamate è lo strumento di debug più utile che Java ti offre gratis.

La gerarchia delle eccezioni

Ogni eccezione è un oggetto, e discendono tutte da Throwable. I due rami che contano:

  • Error: problemi gravi sollevati dalla JVM, come OutOfMemoryError o StackOverflowError. In genere non li catturi.
  • Exception: problemi che il tuo programma può ragionevolmente prevedere e gestire. Al suo interno c'è RuntimeException, il genitore dei bug di tutti i giorni come NullPointerException.
Throwable
├── Error                 (don't catch: OutOfMemoryError, StackOverflowError)
└── Exception
    ├── IOException        (checked)
    ├── SQLException       (checked)
    └── RuntimeException   (unchecked)
        ├── NullPointerException
        ├── ArithmeticException
        └── ArrayIndexOutOfBoundsException

Dato che sono vere classi, un'eccezione può portare con sé un messaggio e puoi interrogarla su se stessa:

getMessage() restituisce il testo dopo i due punti nello stack trace; getClass().getSimpleName() ti dà il nome del tipo di eccezione.

Checked e unchecked

È la distinzione che mette più in difficoltà chi inizia.

  • Le eccezioni unchecked estendono RuntimeException. Di solito indicano un bug nel tuo codice: un null che non ti aspettavi, un indice sbagliato, una divisione per zero. Il compilatore non ti obbliga a gestirle.
  • Le eccezioni checked estendono Exception ma non RuntimeException (per esempio IOException). Rappresentano condizioni fuori dal tuo controllo: un file mancante, una connessione di rete interrotta. Il compilatore ti obbliga a catturarle o a dichiararle.

Se un metodo può lanciare un'eccezione checked, deve dichiararlo con throws, e ogni chiamante deve occuparsene:

Togli throws Exception da main e il codice non compila nemmeno: è il compilatore che fa rispettare il contratto delle eccezioni checked. Le eccezioni unchecked non lo richiedono mai.

Lanciare le tue eccezioni

Non devi solo reagire alle eccezioni: puoi anche sollevarle tu. Usa throw con un nuovo oggetto eccezione per segnalare che un argomento o uno stato non è valido. È molto meglio che restituire un valore magico come -1 e sperare che il chiamante lo controlli.

Includi sempre un messaggio che spieghi cosa non andava e, se possibile, il valore incriminato: quando leggerai lo stack trace in futuro te ne sarai grato. IllegalArgumentException e IllegalStateException sono le due che userai più spesso per validare gli input.

Trappole comuni

  • Ingoiare le eccezioni. Catturare un'eccezione e non fare nulla (un blocco vuoto) nasconde i bug. Come minimo registrala nel log; di solito dovresti gestirla o rilanciarla.
  • Catturare Exception in modo troppo ampio. Catturare la classe base Exception (o peggio Throwable) può nascondere problemi che non intendevi gestire. Cattura il tipo specifico che ti aspetti.
  • Leggere lo stack trace dal basso. Il frame più rilevante è la riga at in alto, non quella in fondo. Parti da lì.
  • Confondere Error con Exception. Non cercare di riprenderti da OutOfMemoryError o StackOverflowError; correggi invece la causa di fondo.

Prossimo passo: try-catch

Ora sai cosa sono le eccezioni e come leggerle. La prossima pagina spiega come gestirle davvero: racchiudere il codice rischioso in un blocco try, recuperare in catch ed eseguire il codice di pulizia in finally, così il programma continua invece di bloccarsi.

Domande frequenti

Cos'è un'eccezione in Java?

Un'eccezione è un oggetto che rappresenta un problema rilevato mentre il programma è in esecuzione, come una divisione per zero, un indice oltre la fine di un array o la chiamata di un metodo su null. Quando il problema si verifica, Java lancia l'eccezione: interrompe il flusso normale e cerca del codice in grado di gestirla. Se nessuno la gestisce, il programma stampa uno stack trace e termina.

Qual è la differenza tra un'eccezione checked e una unchecked in Java?

Le eccezioni checked (sottoclassi di Exception ma non di RuntimeException, per esempio IOException) devono essere catturate oppure dichiarate con throws: il compilatore lo impone. Le eccezioni unchecked (sottoclassi di RuntimeException, per esempio NullPointerException, ArrayIndexOutOfBoundsException) di solito segnalano bug di programmazione e non richiedono dichiarazioni. Anche Error (come OutOfMemoryError) è unchecked e in genere non va catturato.

Come si legge uno stack trace in Java?

Leggilo dall'alto verso il basso. La prima riga indica il tipo di eccezione e il messaggio (per esempio java.lang.ArithmeticException: / by zero). Ogni riga at ... sotto è uno stack frame che mostra il metodo, il file e il numero di riga, partendo dal punto in cui l'eccezione è stata lanciata e risalendo tra i chiamanti. La riga at più in alto è quasi sempre quella da guardare per prima.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA