Eccezioni
Last updated
Last updated
In aggiunta alle normali strutture di controllo del flusso di esecuzione (if, while, for ...) del programma, Java ha un modo per gestire i casi "eccezionali" che possono deviare il normale flusso di esecuzione. Quando un' eccezione avviene durante l' esecuzione del programma, il comportamento di default è di terminare il programma e stampare un messagggio d'errore. Tuttavia Java permette anche di "catturare" (catch) le eccezioni e rispondere in modo differente dal semplice terminazione improvvisa del programma. Questo è ottenuto con lo statement try...catch .
Le eccezioni sono degli oggetti sottoclasse della classe base Throwable e sono orgazinnate in due sottoclassi Error ed Exception. Nella figura alcuna delle eccezioni standard del JDK:
Sottoclassi di java.lang.Error sono errori nel funzionamento del programma all'interno dell'interprete Java durante il funzionamento (es esaurimento memoria java.lang.OutOfMemoryException ); sono errori gravi, che implicano il termine del programma. Essendo legati al funzionamento del programma non possono essere recuperati, il programma va in uno stato in cui l'unica cosa che può fare è terminale l'applicazione (in questo caso è la cosa migliore da fare). Altri tipi d'errore, le eccezioni, sottoclassi di java.lang.Exception, possono essere invece recuperate e vedremo come.
Vediamo come utilizzando Integer.parseInt(String value)
che può lanciare l'eccezione java.lang.NumberFormatException
nel caso la stringa di input non sia possibile convertirla in intero (es input "pippo"):
Ora utilizzando try...catch
per gestire l'eccezione:
Anche il metodo nextInt()
di Scanner
può lanciare un'eccezione java.util.InputMismatchException
in caso in cui l'input non sia convertibile in intero.
Nell'esempio sopra vediamo l'utilizzo di try...catch ...finally
. La parte in finally è sempre eseguita sia che sia lanciata un'eccezione nel blocco try...catch
sia che non ci siano eccezioni.
Nell'esempio sotto vediamo che esiste la forma try...finally
in cui non viene catturata l'eccezione (non c'è catch) ma comunque viene garantita l'esecuzione sempre del blocco finally, sia in caso di nessuna eccezione ma anche quando nel blocco try viene lanciata un'eccezione.
Finora abbiamo utilizzato dei metodi che potevano lanciare un' eccezione: es il metdo parseInt
di Integer
in caso di input "fred" invece di un intero. E se volessi che un mio metodo potesse lanciare un'eccezione al verificarsi di alcune condizioni?
Facciamo l'esempio di una classe Portafoglio
in cui si possono aggiungere soldi e prelevare soldi, se però cerco di prelevare una quantità di denaro che non c'è in cassa, viene lanciata l'eccezione java.lang.IllegalArgimentException
per segnalare che l'operazione non è consentita.
Vediamo l'esempio:
Notiamo l'utilizzo di throw
per generare l'eccezione all'interno del metodo preleva
di Portafoglio
.
Esercizio:
modificate il codice nel main
di TestPortafoglio
per catturare l'eccezione;
Nell'ultimo esempio (quello del Portafoglio
abbiamo visto come "lanciare" un'eccezione: data la classe della eccezione che voglio generare, creo un istanza della classe e tramite trow lancio l'eccezione. Nell'esempio:
In questo caso ho utilizzato una delle eccezioni della libreria standard del JDK (java.lang.IllegalArgumentException
).
Nulla mi impedisce, qualora lo ritenessi opportuno, creare una mia eccezione e utilizzarla come ho fatto per java.lang.IllegalArgumentException
.
Vediamo subilto un esempio:
Definiamo una nostra eccezione:
E' una classe normale come qualsiasi altra classe l'unica cosa è che è sottoclasse di java.lang.RuntimeException
.
Utlizziamola nel nuovo programma e vediamo che non cambia niente rispetto a come è stata utilizzata una delle eccezioni della libreria standard:
Finora negli esempi abbiamo visto tutte eccezioni che era facoltative che fossero gestite tramite il meccanismo di try....catch: se non gestite causavano l'uscita immediata del programma con la print dello stacktrace, il punto, cioè in cui è avvenuta l'eccezione con lo stack delle chiamate.
Le eccezioni sottoclassi di java.lang.RuntimeException
come java.lang.NumberFormatException
o java.lang.IllegalArgumentException
e tryexception.MyException
non obbligano ad essere gestite tramite try...catch.
Esiste un altro genere di eccezioni derivate java.lang.Exception
che Java obbliga a gestire tramite il meccanismo di try...catch. Se non si scrivesse il codice per gestire l'eccezione, il compilatore darebbe errore di compilazione.
Vediamo un esempio di eccezione che deve essere gestita obbligatoriamente e il suo utilizzo nell'esempio del portafoglio:
Da notare inoltre nella dichiarazione del metodo preleva
della classe Portafoglio3_0
il throws (al plurale e non al singolare come invece quando viene lanciata l'eccezione).
Un metodo al cui interno viene lanciata un'eccezione di quelle obbligatorie da essere gestite, ha due possibilità:
o la gestisce al suo interno tramite il try...catch;
o dichiara con throws che il metodo può lanciare l'eccezione. Sarà compito del chiamante gestire l'eccezione o, in modo consapevole, dichiarare a sua volta che anche lui può lanciare (throws) l'eccezione;
Il meccanismo utilizzato da Java per le eccezioni e in particolare per quelle non di tipo java.lang.RuntimeException
, obbliga il programmatore a gestire le situazioni di Exception, tramite il try..catch, o dichiarando, tramite il throws, che il metodo può generare un eccezione.
Esercizi trattati https://github.com/checksound/ProvaEccezioni
Finora abbiamo fatto un'introduzione con alcuni esempi, nel seguente tutorial vedremo gli stessi concetti in modo più sistematico: http://www3.ntu.edu.sg/home/ehchua/programming/java/J5a_ExceptionAssert.html
Partendo dell'esempio del portafoglio scrivere una nuove versione che controlla che non si siano ritirati più di 500 euro a prelievo. In caso che si superi questo limite, anche se magari sul conto ci sono 2000 euro, lanciare l'eccezione SingleWithdrawnLimitException
(withdrawn = prelievo). Costruire un caso per testare questa condizione, simulando il prelievo. La classe Porfolio
genera inoltre ancora l'eccezione AmountWithdrawnException
(o PorfolioException
di prima) in caso in cui l'utente cerchi di prelevare una quantità maggiore di quanto ci sia in cassa: esempio, sul conto ci sono 500 euro e l'utente cerca di prelevare 600 euro, allora il metodo preleva deve generare l'eccezione AmountWithdrawnException
.
Verificate costruendo dei casi in cui verificate il lancio delle le due eccezioni.
Scrivete un'ulteriore classe Porfolio
(potete farlo in un altro package, così conservate le versione precedente) che controlli che il limite del 1500 non sia superato sommando gli ultimi prelievi. Quindi se sul conto ho ancora 2000 euro e tento di ritirare 200 euro ma finora ho già fatto due prelievi da 700 euro ciascuno, con il nuovo prelievo arriverei a 1600 (>1500 limite giornaliero) allora viene lanciata una nuove eccezione DailyWithdrawnLimitException
. Serve quindi una variabile che tenga conto della somma dei prelievi precedenti. Scrivete anche un metodo reset()
della classe Porfolio
che faccia il reset della somma dei prelievi in modo che quando il giorno passa potete riprendere a prelevare. Scrivete un programma in cui testate il caso di DailyWithdrawnLimitException
e poi l'utilizzo di reset per simulare il passaggio della giornata e il fatto che potete continuare a prelevare.