Eccezioni in Java, Parte 1: nozioni di base sulla gestione delle eccezioni

Le eccezioni Java sono i tipi di libreria e le funzionalità del linguaggio utilizzate per rappresentare e gestire gli errori del programma. Se vuoi capire come viene rappresentato il fallimento nel codice sorgente, sei nel posto giusto. Oltre a una panoramica delle eccezioni Java, ti farò iniziare con le funzionalità del linguaggio Java per lanciare oggetti, provare codice che potrebbe non riuscire, rilevare oggetti lanciati e ripulire il codice Java dopo che è stata generata un'eccezione.

Nella prima metà di questo tutorial imparerai a conoscere le caratteristiche del linguaggio di base e i tipi di libreria che esistono da Java 1.0. Nella seconda metà, scoprirai funzionalità avanzate introdotte nelle versioni Java più recenti.

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.

Cosa sono le eccezioni Java?

L'errore si verifica quando il comportamento normale di un programma Java viene interrotto da un comportamento imprevisto. Questa divergenza è nota come eccezione . Ad esempio, un programma tenta di aprire un file per leggerne il contenuto, ma il file non esiste. Java classifica le eccezioni in pochi tipi, quindi consideriamole ciascuna.

Eccezioni verificate

Java classifica le eccezioni derivanti da fattori esterni (come un file mancante) come eccezioni controllate . Il compilatore Java controlla che tali eccezioni siano gestite (corrette) dove si verificano o documentate per essere gestite altrove.

Gestori di eccezioni

Un gestore di eccezioni è una sequenza di codice che gestisce un'eccezione. Interroga il contesto, il che significa che legge i valori salvati dalle variabili che erano nell'ambito al momento in cui si è verificata l'eccezione, quindi utilizza ciò che apprende per ripristinare il programma Java a un flusso di comportamento normale. Ad esempio, un gestore di eccezioni potrebbe leggere un nome di file salvato e richiedere all'utente di sostituire il file mancante.

Eccezioni di runtime (non controllate)

Supponiamo che un programma tenti di dividere un numero intero per intero 0. Questa impossibilità illustra un altro tipo di eccezione, vale a dire un'eccezione di runtime . A differenza delle eccezioni verificate, le eccezioni di runtime derivano tipicamente da codice sorgente scritto male e dovrebbero quindi essere corrette dal programmatore. Poiché il compilatore non controlla che le eccezioni di runtime siano gestite o documentate per essere gestite altrove, puoi pensare a un'eccezione di runtime come un'eccezione non controllata .

Informazioni sulle eccezioni di runtime

Potresti modificare un programma per gestire un'eccezione di runtime, ma è meglio correggere il codice sorgente. Le eccezioni di runtime spesso derivano dal passaggio di argomenti non validi ai metodi di una libreria; il codice di chiamata difettoso dovrebbe essere corretto.

Errori

Alcune eccezioni sono molto gravi perché mettono a rischio la capacità di un programma di continuare l'esecuzione. Ad esempio, un programma tenta di allocare memoria dalla JVM ma non c'è abbastanza memoria libera per soddisfare la richiesta. Un'altra situazione grave si verifica quando un programma tenta di caricare un file di classe tramite una Class.forName()chiamata al metodo, ma il file di classe è danneggiato. Questo tipo di eccezione è noto come errore . Non dovresti mai provare a gestire gli errori da solo perché la JVM potrebbe non essere in grado di ripristinarli.

Eccezioni nel codice sorgente

Un'eccezione può essere rappresentata nel codice sorgente come codice di errore o come oggetto . Presenterò entrambi e ti mostrerò perché gli oggetti sono superiori.

Codici di errore e oggetti

I linguaggi di programmazione come il C utilizzano codici di errore basati su numeri interi per rappresentare l'errore e le ragioni dell'errore, ovvero le eccezioni. Qui ci sono un paio di esempi:

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

La funzione di C chdir()(cambia directory) restituisce un numero intero: 0 in caso di successo o -1 in caso di fallimento. Allo stesso modo, la funzione di C fopen()(file open) restituisce un puntatore non nullo (indirizzo intero) a una FILEstruttura in caso di successo o un puntatore nullo (0) (rappresentato da costante NULL) in caso di fallimento. In entrambi i casi, per identificare l'eccezione che ha causato l'errore, è necessario leggere il errnocodice di errore basato su numeri interi della variabile globale .

I codici di errore presentano alcuni problemi:

  • I numeri interi sono privi di significato; non descrivono le eccezioni che rappresentano. Ad esempio, cosa significa 6?
  • L'associazione del contesto a un codice di errore è scomoda. Ad esempio, potresti voler restituire il nome del file che non è stato possibile aprire, ma dove memorizzerai il nome del file?
  • I numeri interi sono arbitrari, il che può creare confusione durante la lettura del codice sorgente. Ad esempio, specificare if (!chdir("C:\\temp"))( !significa NON) invece di if (chdir("C:\\temp"))testare il fallimento è più chiaro. Tuttavia, 0 è stato scelto per indicare il successo e quindi if (chdir("C:\\temp"))deve essere specificato per verificare il fallimento.
  • I codici di errore sono troppo facili da ignorare, il che può portare a codici difettosi. Ad esempio, il programmatore potrebbe specificare chdir("C:\\temp");e ignorare il if (fp == NULL)controllo. Inoltre, il programmatore non ha bisogno di esaminare errno. Non eseguendo il test di errore, il programma si comporta in modo irregolare quando una delle due funzioni restituisce un indicatore di errore.

Per risolvere questi problemi, Java ha adottato un nuovo approccio alla gestione delle eccezioni. In Java, combiniamo oggetti che descrivono eccezioni con un meccanismo basato sul lancio e la cattura di questi oggetti. Di seguito sono riportati alcuni vantaggi dell'utilizzo di oggetti rispetto al codice di errore per indicare le eccezioni:

  • Un oggetto può essere creato da una classe con un nome significativo. Ad esempio, FileNotFoundException(nel java.iopacchetto) è più significativo di 6.
  • Gli oggetti possono memorizzare il contesto in vari campi. Ad esempio, è possibile memorizzare un messaggio, il nome del file che non è stato possibile aprire, la posizione più recente in cui un'operazione di analisi non è riuscita e / o altri elementi nei campi di un oggetto.
  • Non usi le ifdichiarazioni per testare il fallimento. Invece, gli oggetti eccezione vengono lanciati a un gestore separato dal codice del programma. Di conseguenza, il codice sorgente è più facile da leggere e ha meno probabilità di essere difettoso.

Lanciabile e sue sottoclassi

Java fornisce una gerarchia di classi che rappresentano diversi tipi di eccezioni. Queste classi sono radicati nella java.langdel pacchetto di Throwableclasse, insieme con i suoi Exception, RuntimeExceptione Errorsottoclassi.

Throwableè la superclasse definitiva per quanto riguarda le eccezioni. Solo gli oggetti creati da Throwablee le sue sottoclassi possono essere lanciati (e successivamente catturati). Tali oggetti sono noti come oggetti lanciabili .

Un Throwableoggetto è associato a un messaggio di dettaglio che descrive un'eccezione. Diversi costruttori, inclusa la coppia descritta di seguito, sono forniti per creare un Throwableoggetto con o senza un messaggio di dettaglio:

  • Throwable () crea un Throwablemessaggio senza dettagli. Questo costruttore è appropriato per le situazioni in cui non è presente alcun contesto. Ad esempio, vuoi solo sapere che una pila è vuota o piena.
  • Throwable (String message) crea un Throwablecon messagecome messaggio di dettaglio. Questo messaggio può essere inviato all'utente e / o registrato.

Throwablefornisce il String getMessage()metodo per restituire il messaggio di dettaglio. Fornisce inoltre metodi utili aggiuntivi, che introdurrò in seguito.

La classe Exception

Throwableha due sottoclassi dirette. Una di queste sottoclassi è Exception, che descrive un'eccezione derivante da un fattore esterno (come il tentativo di leggere da un file inesistente). Exceptiondichiara gli stessi costruttori (con elenchi di parametri identici) di Throwablee ogni costruttore richiama la sua Throwablecontroparte. Exceptioneredita Throwablei metodi di; non dichiara nuovi metodi.

Java fornisce molte classi di eccezione che direttamente sottoclasse Exception. Ecco tre esempi:

  • CloneNotSupportedException segnala un tentativo di clonare un oggetto la cui classe non implementa l' Cloneableinterfaccia. Entrambi i tipi sono nel java.langpacchetto.
  • IOException segnala che si è verificato un qualche tipo di errore di I / O. Questo tipo si trova nella java.ioconfezione.
  • ParseException segnala che si è verificato un errore durante l'analisi del testo. Questo tipo può essere trovato nella java.textconfezione.

Notare che ogni Exceptionnome di sottoclasse termina con la parola Exception. Questa convenzione facilita l'identificazione dello scopo della classe.

Di solito sottoclassi Exception(o una delle sue sottoclassi) con le tue classi di eccezione (i cui nomi dovrebbero terminare con Exception). Ecco un paio di esempi di sottoclassi personalizzate:

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

Il primo esempio descrive una classe di eccezione che non richiede un messaggio di dettaglio. È predefinito che il costruttore noargument invoca Exception(), che invoca Throwable().

Il secondo esempio descrive una classe di eccezione il cui costruttore richiede un messaggio di dettaglio e il nome della directory vuota. Il costruttore invoca Exception(String message), che invoca Throwable(String message).

Gli oggetti istanziati da Exceptiono da una delle sue sottoclassi (ad eccezione di RuntimeExceptiono una delle sue sottoclassi) sono eccezioni controllate.

La classe RuntimeException

Exceptionè direttamente sottoclasse da RuntimeException, che descrive un'eccezione molto probabilmente derivante da un codice scritto male. RuntimeExceptiondichiara gli stessi costruttori (con elenchi di parametri identici) di Exceptione ogni costruttore richiama la sua Exceptioncontroparte. RuntimeExceptioneredita Throwablei metodi di. Non dichiara nuovi metodi.

Java fornisce molte classi di eccezione che direttamente sottoclasse RuntimeException. I seguenti esempi sono tutti membri del java.langpacchetto:

  • ArithmeticException segnala un'operazione aritmetica illegale, come il tentativo di dividere un numero intero per 0.
  • IllegalArgumentException segnala che un argomento illegale o inappropriato è stato passato a un metodo.
  • NullPointerException segnala un tentativo di richiamare un metodo o accedere a un campo di istanza tramite il riferimento null.

Gli oggetti istanziati da RuntimeExceptiono da una delle sue sottoclassi sono eccezioni non controllate .

La classe Error

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.