Java Language
Eccezioni e gestione delle eccezioni
Ricerca…
introduzione
Throwable
e i suoi sottotipi possono essere inviati allo stack con la parola chiave throw
e catturati con try…catch
statements.
Sintassi
void someMethod () genera la dichiarazione del metodo SomeException {} //, forza l'intercettazione dei chiamanti del metodo se SomeException è un tipo di eccezione verificata
provare {
someMethod(); //code that might throw an exception
}
catch (SomeException e) {
System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
}
finalmente {
//code that will always run, whether try block finishes or not
}
Cattura un'eccezione con try-catch
Un'eccezione può essere catturata e gestita usando la try...catch
. (In effetti le dichiarazioni di try
prendono altre forme, come descritto in altri esempi su try...catch...finally
e try-with-resources
.)
Prova a prendere con un blocco di cattura
La forma più semplice ha questo aspetto:
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
Il comportamento di un semplice try...catch
è il seguente:
- Le istruzioni nel blocco
try
vengono eseguite. - Se nessuna eccezione è generata dalle dichiarazioni nel blocco
try
, il controllo passa alla successiva dichiarazione dopo iltry...catch
. - Se viene lanciata un'eccezione all'interno del blocco
try
.- L'oggetto eccezione viene verificato per verificare se si tratta di un'istanza di
SomeException
o di un sottotipo. - Se lo è, il blocco
catch
attira l'eccezione:- La variabile
e
è associata all'oggetto eccezione. - Il codice all'interno del blocco
catch
viene eseguito. - Se quel codice genera un'eccezione, l'eccezione appena generata viene propagata al posto di quella originale.
- Altrimenti, il controllo passa alla dichiarazione successiva dopo il
try...catch
.
- La variabile
- Se non lo è, l'eccezione originale continua a propagarsi.
- L'oggetto eccezione viene verificato per verificare se si tratta di un'istanza di
Prova a catturare con più catture
Un try...catch
può anche avere più blocchi catch
. Per esempio:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
Se ci sono più blocchi catch
, vengono provati uno alla volta iniziando dal primo, finché non viene trovata una corrispondenza per l'eccezione. Il gestore corrispondente viene eseguito (come sopra) e quindi il controllo viene passato all'istruzione successiva dopo l'istruzione try...catch
. I blocchi catch
dopo quello che corrisponde vengono sempre saltati, anche se il codice del gestore genera un'eccezione .
La strategia di abbinamento "top down" ha conseguenze per i casi in cui le eccezioni nei blocchi catch
non sono disgiunte. Per esempio:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
Questo snippet di codice genererà "Eccezione" anziché "RuntimeException". Poiché RuntimeException
è un sottotipo di Exception
, il primo (più generale) catch
verrà confrontato. La seconda catch
(più specifica) non verrà mai eseguita.
La lezione da imparare da questo è che i blocchi di catch
più specifici (in termini di tipi di eccezione) dovrebbero apparire per primi, e quelli più generali dovrebbero essere gli ultimi. (Alcuni compilatori Java ti avviseranno se una catch
non può mai essere eseguita, ma questo non è un errore di compilazione.)
Blocchi di cattura multi-eccezione
A partire da Java SE 7, un singolo blocco catch
può gestire un elenco di eccezioni non correlate. Il tipo di eccezione è elencato, separato da un simbolo di barra verticale ( |
). Per esempio:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
Il comportamento di un'eccezione a più eccezioni è un'estensione semplice per il caso a eccezione singola. Il catch
corrisponde se l'eccezione generata soddisfa (almeno) una delle eccezioni elencate.
C'è qualche sottigliezza aggiuntiva nelle specifiche. Il tipo di e
è un'unione sintetica dei tipi di eccezione nell'elenco. Quando viene utilizzato il valore di e
, il suo tipo statico è il supertipo meno comune dell'unione di tipo. Tuttavia, se e
viene ripubblicato all'interno del blocco catch
, i tipi di eccezioni lanciati sono i tipi nell'unione. Per esempio:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
In quanto sopra, IOException
e SQLException
sono eccezioni controllate il cui supertipo minimo comune è Exception
. Ciò significa che il metodo del report
deve corrispondere al report(Exception)
. Tuttavia, il compilatore sa che il throw
può generare solo IOException
o SQLException
. Pertanto, il method
può essere dichiarato come throws IOException, SQLException
piuttosto che throws Exception
. (Che è una buona cosa: vedi Pitfall - Throwable Throwable, Exception, Error o RuntimeException .)
Lanciare un'eccezione
Il seguente esempio mostra le basi del lancio di un'eccezione:
public void checkNumber(int number) throws IllegalArgumentException {
if (number < 0) {
throw new IllegalArgumentException("Number must be positive: " + number);
}
}
L'eccezione è lanciata sulla terza linea. Questa affermazione può essere suddivisa in due parti:
new IllegalArgumentException(...)
sta creando un'istanza della classeIllegalArgumentException
, con un messaggio che descrive l'errore segnalato da tale eccezione.throw ...
lancia quindi l'oggetto eccezione.
Quando viene generata l'eccezione, le istruzioni di chiusura vengono terminate in modo anomalo finché non viene gestita l'eccezione. Questo è descritto in altri esempi.
È buona norma creare e lanciare l'oggetto eccezione in una singola istruzione, come mostrato sopra. È inoltre buona norma includere un messaggio di errore significativo nell'eccezione per aiutare il programmatore a comprendere la causa del problema. Tuttavia, questo non è necessariamente il messaggio che dovresti mostrare all'utente finale. (Per cominciare, Java non ha supporto diretto per l'internazionalizzazione dei messaggi di eccezione.)
Ci sono un paio di altri punti da fare:
Abbiamo dichiarato il
checkNumber
comethrows IllegalArgumentException
. Questo non era strettamente necessario, poichéIllegalArgumentException
è un'eccezione controllata; vedere La gerarchia delle eccezioni Java - Eccezioni non selezionate e controllate . Tuttavia, è buona pratica farlo e includere anche le eccezioni generate dai commenti javadoc di un metodo.Codice immediatamente dopo che una dichiarazione di
throw
è irraggiungibile . Quindi se abbiamo scritto questo:throw new IllegalArgumentException("it is bad"); return;
il compilatore avrebbe segnalato un errore di compilazione per la dichiarazione di
return
.
Eccezione concatenata
Molte eccezioni standard hanno un costruttore con un secondo argomento di cause
oltre all'argomento di message
convenzionale. La cause
consente di concatenare eccezioni. Ecco un esempio.
Per prima cosa definiamo un'eccezione non controllata che la nostra applicazione sta per gettare quando incontra un errore non recuperabile. Si noti che abbiamo incluso un costruttore che accetta un argomento di cause
.
public class AppErrorException extends RuntimeException {
public AppErrorException() {
super();
}
public AppErrorException(String message) {
super(message);
}
public AppErrorException(String message, Throwable cause) {
super(message, cause);
}
}
Successivamente, ecco un codice che illustra il concatenamento delle eccezioni.
public String readFirstLine(String file) throws AppErrorException {
try (Reader r = new BufferedReader(new FileReader(file))) {
String line = r.readLine();
if (line != null) {
return line;
} else {
throw new AppErrorException("File is empty: " + file);
}
} catch (IOException ex) {
throw new AppErrorException("Cannot read file: " + file, ex);
}
}
Il throw
all'interno del blocco try
rileva un problema e lo segnala tramite un'eccezione con un semplice messaggio. Al contrario, il throw
all'interno del blocco catch
sta gestendo IOException
avvolgendolo in una nuova (controllata) eccezione. Tuttavia, non sta gettando via l'eccezione originale. Passando la IOException
come cause
, la registriamo in modo che possa essere stampata nello stacktrace, come spiegato in Creazione e lettura di stacktraces .
Eccezioni personalizzate
Nella maggior parte dei casi, è più semplice da un punto di vista della progettazione del codice utilizzare le classi di Exception
generiche esistenti quando si generano eccezioni. Questo è particolarmente vero se hai solo bisogno dell'eccezione per portare un semplice messaggio di errore. In tal caso, RuntimeException di solito è preferito, poiché non è un'eccezione controllata. Esistono altre classi di eccezioni per classi comuni di errori:
- UnsupportedOperationException - una determinata operazione non è supportata
- IllegalArgumentException : un valore di parametro non valido è stato passato a un metodo
- IllegalStateException : la tua API ha raggiunto internamente una condizione che non dovrebbe mai accadere o che si verifica a seguito dell'utilizzo dell'API in modo non valido
I casi in cui si vuole utilizzare una classe eccezione personalizzata sono i seguenti:
- Stai scrivendo un'API o una libreria per l'utilizzo da parte di altri e desideri consentire agli utenti della tua API di essere in grado di catturare e gestire in modo specifico le eccezioni dalla tua API e di distinguere tali eccezioni da altre eccezioni più generiche .
- Stai lanciando eccezioni per un tipo specifico di errore in una parte del tuo programma, che vuoi catturare e gestire in un'altra parte del tuo programma, e vuoi essere in grado di distinguere questi errori da altri errori più generici.
È possibile creare eccezioni personalizzate estendendo RuntimeException
per un'eccezione non controllata o verificata l'eccezione estendendo qualsiasi Exception
che non sia anche sottoclasse di RuntimeException , in quanto:
Le sottoclassi di Eccezione che non sono anche sottoclassi di RuntimeException sono eccezioni controllate
public class StringTooLongException extends RuntimeException {
// Exceptions can have methods and fields like other classes
// those can be useful to communicate information to pieces of code catching
// such an exception
public final String value;
public final int maximumLength;
public StringTooLongException(String value, int maximumLength){
super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
this.value = value;
this.maximumLength = maximumLength;
}
}
Quelli possono essere usati solo come eccezioni predefinite:
void validateString(String value){
if (value.length() > 30){
throw new StringTooLongException(value, 30);
}
}
E i campi possono essere utilizzati dove viene rilevata e gestita l'eccezione:
void anotherMethod(String value){
try {
validateString(value);
} catch(StringTooLongException e){
System.out.println("The string '" + e.value +
"' was longer than the max of " + e.maximumLength );
}
}
Tieni presente che, secondo la documentazione Java di Oracle :
[...] Se ci si può ragionevolmente aspettare che un client recuperi da un'eccezione, rendi un'eccezione controllata. Se un client non può eseguire operazioni di ripristino dall'eccezione, renderlo un'eccezione non controllata.
Di Più:
La dichiarazione try-with-resources
Come illustra l'esempio di dichiarazione try-catch-final , il cleanup delle risorse che utilizza una clausola finally
richiede una quantità significativa di codice "boiler-plate" per implementare correttamente le edge case. Java 7 fornisce un modo molto più semplice per affrontare questo problema nella forma dell'istruzione try-with-resources .
Cos'è una risorsa?
Java 7 ha introdotto l'interfaccia java.lang.AutoCloseable
per consentire la gestione delle classi utilizzando l'istruzione try-with-resources . Le istanze di classi che implementano AutoCloseable
sono indicate come risorse . Questi in genere devono essere smaltiti in modo tempestivo piuttosto che affidarsi al garbage collector per smaltirli.
L'interfaccia AutoCloseable
definisce un singolo metodo:
public void close() throws Exception
Un metodo close()
dovrebbe disporre della risorsa in modo appropriato. La specifica afferma che dovrebbe essere sicuro chiamare il metodo su una risorsa che è già stata eliminata. Inoltre, le classi che implementano l' Autocloseable
sono fortemente incoraggiate a dichiarare il metodo close()
per generare un'eccezione più specifica di Exception
, o nessuna eccezione.
Una vasta gamma di classi e interfacce Java standard implementano AutoCloseable
. Questi includono:
-
InputStream
,OutputStream
e le loro sottoclassi -
Reader
,Writer
e le loro sottoclassi -
Socket
eServerSocket
e relative sottoclassi -
Channel
e le sue sottoclassi, e - il JDBC interfaccia
Connection
,Statement
eResultSet
e le loro sottoclassi.
Anche le classi di applicazioni e di terze parti possono farlo.
La dichiarazione base di prova con la risorsa
La sintassi di un try-with-resources si basa su forme classiche try-catch , try-finally e try-catch-finally . Ecco un esempio di una forma "base"; cioè la forma senza un catch
o, finally
.
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
Le risorse da gestire sono dichiarate come variabili nella (...)
sezione dopo la clausola try
. Nell'esempio sopra, dichiariamo un stream
variabili di risorsa e lo inizializziamo su PrintStream
appena creato.
Una volta che le variabili della risorsa sono state inizializzate, viene eseguito il blocco try
. Al termine, stream.close()
verrà chiamato automaticamente per garantire che la risorsa non perda. Si noti che la chiamata close()
avviene indipendentemente dal completamento del blocco.
Le dichiarazioni avanzate di try-with-resource
L'istruzione try-with-resources può essere migliorata con catch
blocchi catch
e finally
, come con la sintassi pre-Java 7 try-catch-finally . Il seguente frammento di codice aggiunge un blocco catch
al precedente per gestire l' PrintStream
FileNotFoundException
che può essere PrintStream
costruttore PrintStream
:
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
} catch (FileNotFoundException ex) {
System.err.println("Cannot open the file");
} finally {
System.err.println("All done");
}
Se l'inizializzazione della risorsa o il blocco try genera l'eccezione, verrà eseguito il blocco catch
. Il blocco finally
verrà sempre eseguito, come in una dichiarazione try-catch-finally convenzionale.
Ci sono un paio di cose da notare però:
- La variabile di risorsa è fuori ambito nel
catch
efinally
blocchi. - La pulizia delle risorse avverrà prima che l'istruzione tenti di far corrispondere il blocco
catch
. - Se la pulizia automatica delle risorse ha generato un'eccezione, potrebbe essere catturata in uno dei blocchi
catch
.
Gestire più risorse
I frammenti di codice sopra mostrano una singola risorsa che viene gestita. In effetti, try-with-resources può gestire più risorse in un'unica istruzione. Per esempio:
try (InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2)) {
// Copy 'is' to 'os'
}
Questo si comporta come ti aspetteresti. Sia is
che os
vengono chiusi automaticamente alla fine del blocco try
. Ci sono un paio di punti da notare:
- Le inizializzazioni si verificano nell'ordine di codice e gli inizializzatori di variabili di risorse successive possono utilizzare i valori di quelli precedenti.
- Tutte le variabili di risorsa inizializzate correttamente verranno eliminate.
- Le variabili delle risorse vengono pulite in ordine inverso rispetto alle loro dichiarazioni.
Pertanto, nell'esempio di cui sopra, is
è inizializzato prima os
e ripulito dopo di essa, e is
verrà pulito se c'è un'eccezione durante l'inizializzazione os
.
Equivalenza di try-with-resource e try-catch-finally classico
La specifica del linguaggio Java specifica il comportamento delle forme try-with-resource in termini della classica dichiarazione try-catch-finally . (Si prega di fare riferimento al JLS per tutti i dettagli.)
Ad esempio, questa base di prova con risorsa :
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
è definito come equivalente a questo try-catch-finally :
// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");
// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;
// The actual try block
try {
stream.println("Hello world!");
} catch (Throwable t) {
// If an exception is thrown, remember it for the finally block
primaryException = t;
throw t;
} finally {
if (primaryException == null) {
// If no exception was thrown so far, exceptions thrown in close() will
// not be caught and therefore be passed on to the enclosing code.
stream.close();
} else {
// If an exception has already been thrown, any exception thrown in
// close() will be suppressed as it is likely to be related to the
// previous exception. The suppressed exception can be retrieved
// using primaryException.getSuppressed().
try {
stream.close();
} catch (Throwable suppressedException) {
primaryException.addSuppressed(suppressedException);
}
}
}
(Il JLS specifica che le variabili t
e primaryException
effettive saranno invisibili al normale codice Java.)
La forma migliorata di try-with-resources è specificata come un'equivalenza con il modulo base. Per esempio:
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
è equivalente a:
try {
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
}
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
Creazione e lettura di stacktraces
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:
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();
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
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.
Gestione di InterruptedException
InterruptedException
è una bestia che confonde - si presenta in metodi apparentemente innocui come Thread.sleep()
, ma Thread.sleep()
modo errato porta a un codice difficile da gestire che si comporta male negli ambienti concorrenti.
Nella sua forma più semplice, se viene rilevata un'interruzione di InterruptedException
, significa che qualcuno, da qualche parte, ha chiamato Thread.interrupt()
sul thread in cui è attualmente in esecuzione il codice. Potresti essere incline a dire "È il mio codice! Non lo interromperò mai! " e quindi fare qualcosa del genere:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
Ma questo è esattamente il modo sbagliato di gestire un evento "impossibile" che si verifica. Se sai che la tua applicazione non incontrerà mai un'interruzione di InterruptedException
, dovresti trattare tale evento come una grave violazione delle ipotesi del tuo programma e uscire il prima possibile.
Il modo corretto di gestire un interrupt "impossibile" è come questo:
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
Questo fa due cose; ripristina innanzitutto lo stato di interruzione del thread (come se l' InterruptedException
non fosse stato gettato in primo luogo), quindi lancia un AssertionError
indica che gli invarianti di base della tua applicazione sono stati violati. Se si è certi che non si interromperà mai il thread, questo codice viene eseguito in questo modo, poiché il blocco catch
non dovrebbe mai essere raggiunto.
L'uso della classe Uninterruptibles
di Guava aiuta a semplificare questo schema; chiamando Uninterruptibles.sleepUninterruptibly()
ignora lo stato interrotto di un thread fino a quando la durata del sonno è scaduta (a quel punto viene ripristinata per le chiamate successive da ispezionare e lanciare la propria InterruptedException
). Se sai che non interromperesti mai questo codice, eviterai in modo sicuro di dover avvolgere le tue chiamate a riposo in un blocco try-catch.
Più spesso, tuttavia, non è possibile garantire che il thread non verrà mai interrotto. In particolare se stai scrivendo un codice che verrà eseguito da un Executor
o da qualche altra gestione dei thread, è fondamentale che il tuo codice risponda prontamente agli interrupt, altrimenti l'applicazione si fermerà o si bloccherà.
In questi casi, la cosa migliore da fare è in genere consentire a InterruptedException
di propagare lo stack delle chiamate, aggiungendo una throws InterruptedException
di throws InterruptedException
a ciascun metodo a turno. Questo può sembrare imbarazzante, ma in realtà è una proprietà desiderabile - le firme del tuo metodo ora indicano ai chiamanti che risponderanno prontamente alle interruzioni.
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
In casi limitati (ad es. Durante l'override di un metodo che non throw
eccezioni controllate) è possibile ripristinare lo stato interrotto senza generare un'eccezione, aspettandosi che qualsiasi codice venga eseguito accanto a gestire l'interrupt. Questo ritarda la gestione dell'interruzione ma non la sopprime completamente.
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}
La gerarchia delle eccezioni Java - Eccezioni non selezionate e controllate
Tutte le eccezioni Java sono istanze di classi nella gerarchia di classi Exception. Questo può essere rappresentato come segue:
-
java.lang.Throwable
- Questa è la classe base per tutte le classi di eccezioni. I suoi metodi e costruttori implementano una gamma di funzionalità comuni a tutte le eccezioni.-
java.lang.Exception
- Questa è la superclasse di tutte le normali eccezioni.- varie classi di eccezioni standard e personalizzate.
-
java.lang.RuntimeException
- Questa è la superclasse di tutte le eccezioni normali che sono eccezioni non controllate .- varie classi di eccezioni di runtime standard e personalizzate.
-
java.lang.Error
- Questa è la superclasse di tutte le eccezioni di "errore fatale".
-
Gli appunti:
- La distinzione tra le eccezioni controllate e non controllate è descritta di seguito.
- La
Throwable
,Exception
eRuntimeException
deve essere considerataabstract
; vedi Pitfall - Throwable Throwable, Exception, Error o RuntimeException . - Le eccezioni di
Error
vengono generate dalla JVM in situazioni in cui non sarebbe sicuro o imprudente per un'applicazione tentare di ripristinare. - Non sarebbe saggio dichiarare sottotipi personalizzati di
Throwable
. Gli strumenti e le librerie Java possono assumere cheError
edException
sono gli unici sottotipi diretti diThrowable
e si comportanoThrowable
se tale presupposto non è corretto.
Controllato contro Eccezioni non selezionate
Una delle critiche al supporto delle eccezioni in alcuni linguaggi di programmazione è che è difficile sapere quali eccezioni potrebbero generare un determinato metodo o procedura. Dato che un'eccezione non gestita può causare l'arresto anomalo di un programma, ciò può rendere le eccezioni una fonte di fragilità.
Il linguaggio Java affronta questo problema con il meccanismo di eccezione verificato. Innanzitutto, Java classifica le eccezioni in due categorie:
Le eccezioni controllate rappresentano in genere gli eventi previsti che un'applicazione dovrebbe essere in grado di gestire. Ad esempio,
IOException
e i suoi sottotipi rappresentano condizioni di errore che possono verificarsi nelle operazioni di I / O. Gli esempi includono, l'apertura di file non avviene perché un file o una directory non esiste, le letture e le scritture di rete non funzionano perché una connessione di rete è stata interrotta e così via.Le eccezioni non controllate tipicamente rappresentano eventi imprevisti che un'applicazione non è in grado di gestire. Questi sono in genere il risultato di un bug nell'applicazione.
(Di seguito, "gettato" si riferisce a qualsiasi eccezione lanciata esplicitamente (da un'istruzione throw
), o implicitamente (in un dereferenziamento fallito, digitare cast e così via). Allo stesso modo, "propagated" si riferisce a un'eccezione che è stata lanciata in un chiamata nidificata e non catturata all'interno di quella chiamata. Il seguente codice di esempio illustrerà questo.)
La seconda parte del meccanismo di eccezione verificata è che esistono restrizioni sui metodi in cui può verificarsi un'eccezione verificata:
- Quando un'eccezione controllata viene lanciata o propagata in un metodo, deve essere rilevata dal metodo o elencata nella clausola di
throws
del metodo. (Il significato della clausolathrows
è descritto in questo esempio ). - Quando un'eccezione controllata viene lanciata o propagata in un blocco di inizializzazione, deve essere catturata dal blocco.
- Un'eccezione verificata non può essere propagata da una chiamata di metodo in un'espressione di inizializzazione del campo. (Non c'è modo di cogliere tale eccezione).
In breve, un'eccezione controllata deve essere gestita o dichiarata.
Queste restrizioni non si applicano alle eccezioni non controllate. Ciò include tutti i casi in cui un'eccezione viene lanciata implicitamente, poiché tutti questi casi generano eccezioni non controllate.
Esempi di eccezioni controllati
Questi snippet di codice hanno lo scopo di illustrare le restrizioni delle eccezioni controllate. In ogni caso, mostriamo una versione del codice con un errore di compilazione e una seconda versione con l'errore corretto.
// This declares a custom checked exception.
public class MyException extends Exception {
// constructors omitted.
}
// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
// constructors omitted.
}
Il primo esempio mostra come le eccezioni verificate esplicitamente generate possono essere dichiarate come "lanciate" se non devono essere gestite nel metodo.
// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
Il secondo esempio mostra come può essere gestita un'eccezione verificata propagata.
// INCORRECT
public void methodWithPropagatedCheckedException() {
InputStream is = new FileInputStream("someFile.txt"); // Compilation error
// FileInputStream throws IOException or a subclass if the file cannot
// be opened. IOException is a checked exception.
...
}
// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
InputStream is = new FileInputStream("someFile.txt");
...
}
// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
try {
InputStream is = new FileInputStream("someFile.txt");
...
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
}
L'esempio finale mostra come gestire un'eccezione controllata in un inizializzatore di campo statico.
// INCORRECT
public class Test {
private static final InputStream is =
new FileInputStream("someFile.txt"); // Compilation error
}
// CORRECTED
public class Test {
private static final InputStream is;
static {
InputStream tmp = null;
try {
tmp = new FileInputStream("someFile.txt");
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
is = tmp;
}
}
Si noti che in questo ultimo caso, abbiamo anche a che fare con i problemi che is
non possono essere assegnati a più di una volta, ma anche deve essere assegnato a, anche nel caso di un'eccezione.
introduzione
Le eccezioni sono errori che si verificano quando un programma è in esecuzione. Considera il programma Java sotto il quale si dividono due numeri interi.
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
result = a / b;
System.out.println("Result = " + result);
}
}
Ora compiliamo ed eseguiamo il codice precedente e vediamo l'output per una divisione tentata per zero:
Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Division.main(Disivion.java:14)
La divisione per zero è un'operazione non valida che produce un valore che non può essere rappresentato come un numero intero. Java si occupa di questo lanciando un'eccezione . In questo caso, l'eccezione è un'istanza della classe ArithmeticException .
Nota: l'esempio sulla creazione e la lettura delle tracce dello stack spiega cosa significa l'output dopo i due numeri.
L'utilità di un'eccezione è il controllo del flusso che consente. Senza usare eccezioni, una soluzione tipica a questo problema potrebbe essere quella di verificare prima se b == 0
:
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
if (b == 0) {
System.out.println("You cannot divide by zero.");
return;
}
result = a / b;
System.out.println("Result = " + result);
}
}
Questo stampa il messaggio You cannot divide by zero.
alla console e chiude il programma in modo aggraziato quando l'utente tenta di dividere per zero. Un modo equivalente di affrontare questo problema tramite la gestione delle eccezioni sarebbe quello di sostituire il controllo del flusso if
con un blocco try-catch
:
...
a = input.nextInt();
b = input.nextInt();
try {
result = a / b;
}
catch (ArithmeticException e) {
System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
return;
}
...
Un blocco catch try viene eseguito come segue:
- Inizia l'esecuzione del codice nel blocco
try
. - Se si verifica un'eccezione nel blocco try, interrompere immediatamente e verificare se questa eccezione è catturata dal blocco
catch
(in questo caso, quando Exception è un'istanza diArithmeticException
). - Se l'eccezione viene catturata , viene assegnata alla variabile
e
e il bloccocatch
viene eseguito. - Se il blocco
try
ocatch
è completato (ovvero non si verificano eccezioni non rilevate durante l'esecuzione del codice), continuare ad eseguire il codice sotto il bloccotry-catch
.
In genere, è consigliabile utilizzare la gestione delle eccezioni come parte del normale controllo del flusso di un'applicazione in cui il comportamento sarebbe altrimenti indefinito o inatteso. Ad esempio, invece di restituire null
quando un metodo fallisce, solitamente è meglio lanciare un'eccezione in modo che l'applicazione che fa uso del metodo possa definire il proprio controllo di flusso per la situazione tramite la gestione delle eccezioni del tipo illustrato sopra. In un certo senso, questo aggira il problema di dover restituire un particolare tipo , poiché uno qualsiasi dei molteplici tipi di eccezioni può essere lanciato per indicare il problema specifico che si è verificato.
Per ulteriori consigli su come e in che modo non usare le eccezioni, fare riferimento a Insidie di Java - Utilizzo delle eccezioni
Restituisci le dichiarazioni in try catch block
Sebbene sia una cattiva pratica, è possibile aggiungere più istruzioni di ritorno in un blocco di gestione delle eccezioni:
public static int returnTest(int number){
try{
if(number%2 == 0) throw new Exception("Exception thrown");
else return x;
}
catch(Exception e){
return 3;
}
finally{
return 7;
}
}
Questo metodo restituirà sempre 7 poiché il blocco finally associato al blocco try / catch viene eseguito prima che venga restituito qualcosa. Ora, come finalmente ha return 7;
, questo valore sostituisce i valori di risposta try / catch.
Se il blocco catch restituisce un valore primitivo e tale valore primitivo viene successivamente modificato nel blocco finally, verrà restituito il valore restituito nel blocco catch e le modifiche dal blocco finally verranno ignorate.
L'esempio seguente stamperà "0", non "1".
public class FinallyExample {
public static void main(String[] args) {
int n = returnTest(4);
System.out.println(n);
}
public static int returnTest(int number) {
int returnNumber = 0;
try {
if (number % 2 == 0)
throw new Exception("Exception thrown");
else
return returnNumber;
} catch (Exception e) {
return returnNumber;
} finally {
returnNumber = 1;
}
}
}
Funzionalità avanzate di Eccezioni
Questo esempio illustra alcune funzioni avanzate e casi d'uso per Eccezioni.
Esaminando programmaticamente il callstack
L'uso principale degli stacktraces delle eccezioni consiste nel fornire informazioni su un errore dell'applicazione e il relativo contesto in modo che il programmatore possa diagnosticare e risolvere il problema. A volte può essere usato per altre cose. Ad esempio, una classe SecurityManager
potrebbe dover esaminare lo stack di chiamate per decidere se il codice che sta effettuando una chiamata deve essere considerato attendibile.
È possibile utilizzare le eccezioni per esaminare lo stack di chiamate in modo programmatico come segue:
Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());
Ci sono alcuni avvertimenti importanti su questo:
Le informazioni disponibili in
StackTraceElement
sono limitate. Non ci sono più informazioni disponibili di quelle visualizzate daprintStackTrace
. (I valori delle variabili locali nel frame non sono disponibili.)I javadoc per
getStackTrace()
indicano che una JVM può lasciare i frame:Alcune macchine virtuali possono, in alcune circostanze, omettere uno o più frame dello stack dall'analisi dello stack. Nel caso estremo, una macchina virtuale che non ha informazioni sulla traccia di stack relative a questo gettabile è autorizzata a restituire una matrice a lunghezza zero da questo metodo.
Ottimizzazione della costruzione di eccezioni
Come accennato altrove, la costruzione di un'eccezione è piuttosto costosa in quanto comporta l'acquisizione e la registrazione di informazioni su tutti i frame di stack sul thread corrente. A volte, sappiamo che quell'informazione non verrà mai usata per una determinata eccezione; ad esempio, lo stacktrace non verrà mai stampato. In tal caso, esiste un trucco di implementazione che è possibile utilizzare in un'eccezione personalizzata per impedire che le informazioni vengano acquisite.
Le informazioni sul frame dello stack necessarie per gli stacktraces, vengono acquisite quando i costruttori Throwable
chiamano il metodo Throwable.fillInStackTrace()
. Questo metodo è public
, il che significa che una sottoclasse può sovrascriverla. Il trucco è scavalcare il metodo ereditato da Throwable
con uno che non fa nulla; per esempio
public class MyException extends Exception {
// constructors
@Override
public void fillInStackTrace() {
// do nothing
}
}
Il problema con questo approccio è che un'eccezione che sovrascrive fillInStackTrace()
non può mai acquisire lo stacktrace ed è inutile negli scenari in cui ne hai bisogno.
Cancellazione o sostituzione dello stacktrace
In alcune situazioni, lo stacktrace per un'eccezione creata nel modo normale contiene informazioni errate o informazioni che lo sviluppatore non desidera rivelare all'utente. Per questi scenari, è possibile utilizzare Throwable.setStackTrace
per sostituire la matrice di oggetti StackTraceElement
che contiene le informazioni.
Ad esempio, è possibile utilizzare quanto segue per eliminare le informazioni sullo stack di un'eccezione:
exception.setStackTrace(new StackTraceElement[0]);
Eccezioni soppresse
Java 7 ha introdotto il costrutto try-with-resources e il concetto associato di soppressione delle eccezioni. Considera il seguente frammento:
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
// do stuff
int temp = 0 / 0; // throws an ArithmeticException
}
Quando viene lanciata l'eccezione, la try
chiamerà close()
su w
che svuoterà qualsiasi output bufferizzato e quindi chiuderà FileWriter
. Ma cosa succede se viene generata una IOException
mentre si scarica l'output?
Quello che succede è che ogni eccezione che viene lanciata mentre si ripulisce una risorsa viene soppressa . L'eccezione viene rilevata e aggiunta all'elenco delle eccezioni soppresse dell'eccezione primaria. Quindi il try-with-resources continuerà con la pulizia delle altre risorse. Infine, l'eccezione primaria verrà riconsiderata.
Un modello simile si verifica se un'eccezione viene generata durante l'inizializzazione della risorsa o se il blocco try
completato normalmente. La prima eccezione generata diventa l'eccezione principale e quelli successivi derivanti dalla pulizia vengono eliminati.
Le eccezioni soppresse possono essere recuperate dall'oggetto eccezione principale chiamando getSuppressedExceptions
.
Le dichiarazioni try-finally e try-catch-finally
L'istruzione try...catch...finally
combina la gestione delle eccezioni con il codice clean-up. Il blocco finally
contiene il codice che verrà eseguito in tutte le circostanze. Questo li rende adatti per la gestione delle risorse e altri tipi di pulizia.
Prova-finalmente
Ecco un esempio della forma più semplice ( try...finally
):
try {
doSomething();
} finally {
cleanUp();
}
Il comportamento del try...finally
è il seguente:
- Il codice nel blocco
try
viene eseguito. - Se non è stata lanciata alcuna eccezione nel blocco
try
:- Il codice nel blocco
finally
viene eseguito. - Se il blocco
finally
genera un'eccezione, quell'eccezione viene propagata. - Altrimenti, il controllo passa alla successiva dichiarazione dopo il
try...finally
.
- Il codice nel blocco
- Se è stata generata un'eccezione nel blocco try:
- Il codice nel blocco
finally
viene eseguito. - Se il blocco
finally
genera un'eccezione, quell'eccezione viene propagata. - Altrimenti, l'eccezione originale continua a propagarsi.
- Il codice nel blocco
Il codice all'interno del blocco finally
verrà sempre eseguito. (Le uniche eccezioni sono se viene chiamato System.exit(int)
o se i panni della JVM.) Quindi un blocco finally
è il codice posto corretto che deve sempre essere eseguito; ad esempio chiudere file e altre risorse o rilasciare blocchi.
try-catch-finally
Il nostro secondo esempio mostra come catch
e, finally
può essere utilizzato insieme. Illustra anche che pulire le risorse non è semplice.
// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
result = reader.readLine();
} catch (IOException ex) {
Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore / discard this exception
}
}
}
L'insieme completo di (ipotetici) comportamenti di try...catch...finally
in questo esempio sono troppo complicati per descrivere qui. La versione semplice è che il codice nel blocco finally
verrà sempre eseguito.
Guardando questo dal punto di vista della gestione delle risorse:
- Dichiariamo la "risorsa" (cioè la variabile del
reader
) prima del bloccotry
modo che sia in scope per il bloccofinally
. - Inserendo il
new FileReader(...)
, ilcatch
è in grado di gestire qualsiasi eccezioneIOError
generata quando si apre il file. - Abbiamo bisogno di un
reader.close()
nel bloccofinally
perché ci sono alcuni percorsi di eccezione che non possiamo intercettare né nel bloccotry
né nel bloccocatch
. - Tuttavia, poiché un'eccezione potrebbe essere stata lanciata prima che il
reader
fosse inizializzato, abbiamo anche bisogno di un testnull
esplicito. - Infine, la chiamata a
reader.close()
potrebbe (ipoteticamente) generare un'eccezione. Non ci interessa, ma se non rileviamo l'eccezione alla fonte, avremmo bisogno di occuparci ulteriormente dello stack delle chiamate.
Java 7 e versioni successive forniscono una sintassi alternativa try-with-resources che semplifica notevolmente la pulizia delle risorse.
La clausola 'getta' in una dichiarazione di metodo
Il meccanismo delle eccezioni controllate di Java richiede che il programmatore dichiari che determinati metodi potrebbero generare eccezioni controllate specificate. Questo viene fatto usando la clausola throws
. Per esempio:
public class OddNumberException extends Exception { // a checked exception
}
public void checkEven(int number) throws OddNumberException {
if (number % 2 != 0) {
throw new OddNumberException();
}
}
Il throws OddNumberException
dichiara che una chiamata a checkEven
potrebbe generare un'eccezione di tipo OddNumberException
.
Una clausola throws
può dichiarare un elenco di tipi e può includere eccezioni non verificate e eccezioni controllate.
public void checkEven(Double number)
throws OddNumberException, ArithmeticException {
if (!Double.isFinite(number)) {
throw new ArithmeticException("INF or NaN");
} else if (number % 2 != 0) {
throw new OddNumberException();
}
}
Qual è il punto di dichiarare le eccezioni non controllate come generate?
La clausola throws
in una dichiarazione di metodo ha due scopi:
Indica al compilatore quali eccezioni vengono lanciate in modo che il compilatore possa segnalare eccezioni non verificate (controllate) come errori.
Indica a un programmatore che sta scrivendo il codice che chiama il metodo quali eccezioni aspettarsi. A tale scopo, fa spesso intendere di includere eccezioni non selezionate in una lista di
throws
.
Nota: che la lista dei throws
viene anche utilizzata dallo strumento javadoc durante la generazione della documentazione API e da un tipico suggerimento del metodo "testo hover" dell'IDE.
Tiri e metodo di esclusione
La clausola throws
fa parte della firma di un metodo allo scopo di sovrascrivere il metodo. Un metodo di override può essere dichiarato con lo stesso insieme di eccezioni verificate come generate dal metodo sottoposto a override o con un sottoinsieme. Tuttavia, il metodo di sovrascrittura non può aggiungere eccezioni controllate supplementari. Per esempio:
@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
...
@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
...
class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}
@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses
@Override
public void checkEven(Double number) throws IOExcepion // ERROR
Il motivo di questa regola è che se un metodo sovrascritto può generare un'eccezione verificata che il metodo sottoposto a override non è in grado di generare, ciò interromperà la sostituibilità del tipo.