Eccezioni

Introduzione eccezioni

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:

Gerarchia classi delle eccezioni

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.

Esempi eccezioni

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"):

package tryexception;

import java.util.Scanner;

/**
 * Esempio programma in cui l'eccezione non è catturata per gestirla.
 * Es: l'input "fred" causerebbe eccezione del metodo Integer.parseInt()
 * @author cam
 */
public class TestExceptionReadInt {

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		System.out.println("Digita un intero:");
		
		String line = in.next();
		int intValue = Integer.parseInt(line);
	    
		System.out.println("Valore: " + intValue);
		in.close();

	}

}

Ora utilizzando try...catch per gestire l'eccezione:

package tryexception;

import java.util.Scanner;

/**
 * Eccezione gestita tramite try...catch
 * 
 * @author cam
 *
 */
public class TestExceptionReadIntWithCatch {

	public static void main(String[] args) {
		
		Scanner in = new Scanner(System.in);
		System.out.println("Digita un intero:");
		
		String line = in.next();
		
		try {
			int intValue = Integer.parseInt(line);
	    	System.out.println("Valore: " + intValue);
		} catch (NumberFormatException e) {
			System.out.println(e.getMessage());
		}
		
		System.out.println("DOPO CATCH");
		in.close();
		
	}

}

Anche il metodo nextInt() di Scanner può lanciare un'eccezione java.util.InputMismatchException in caso in cui l'input non sia convertibile in intero.

package tryexception;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * Esempio cattura dell'eccezione java.util.InputMismatchException 
 * 
 * @author cam
 *
 */
public class TestExceptionReadIntWithFinally {

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		System.out.println("Digita un intero:");
		
		try {
			int intValue = in.nextInt();
			System.out.println("Valore: " + intValue);
		} catch (InputMismatchException e) {
			System.out.println("Exception message: " + e.getMessage());
		} finally {
			System.out.println("SONO IN FINALLY!!!");
			in.close();
		}
	}

}

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.

package tryexception;

import java.util.Scanner;

/**
 * Esempio per evidenziare l'utilizzo di finally nella gestione delle eccezioni, 
 * senza il catch.
 * 
 * @author cam
 *
 */
public class TestExceptionReadIntOnlyFinally {

	public static void main(String[] args) {
		
		Scanner in = new Scanner(System.in);
		
		System.out.println("Digita un intero:");
		
		try {
			int intValue = in.nextInt();
		
			System.out.println("Valore: " + intValue);
		} finally {
			System.out.println("SONO IN FINALLY!!!");
			in.close();
		}
	}

}

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:

package tryexception;

class Portafoglio {
	
	private int disponibilita = 0;
	
	/**
	 * 
	 * @param value
	 * @return la disponibilità ad operazione avvenuta
	 */
	public int versa(int value) {
		this.disponibilita += value;
		return this.disponibilita;
	}
	
	/**
	 * 
	 * @param value
	 * @return la disponibilità ad operazione avvenuta
	 * 
	 * @throws IllegalArgumentException se la disponibilità è < della richiesta di 
	 * prelievo.
	 * 
	 */
	public int preleva(int value) {
		if (this.disponibilita < value) 
			throw new IllegalArgumentException("Disponiblità: " + this.disponibilita 
					+ " < richiesta prelievo: " + value);
		
		this.disponibilita -= value;
		return this.disponibilita;
	}
	
	public int getDisponibilita() {
		return this.disponibilita;
	}
	
}

public class TestPortafoglio {
    	
	public static void main(String[] args) {
		
		Portafoglio portLucia = new Portafoglio();
		
		portLucia.versa(400);
		portLucia.preleva(600);
		
	}

}

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;

Creare una nostra 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:

throw new IllegalArgumentException("Disponiblità: " + this.disponibilita 
					+ " < richiesta prelievo: " + value);

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:

package tryexception;

public class MyException extends RuntimeException {
	
	public MyException(String message) {
		super(message);
	}
}

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:

package tryexception;

class Portafoglio2_0 {
	
	private int disponibilita = 0;
	
	/**
	 * 
	 * @param value
	 * @return la disponibilità ad operazione avvenuta
	 */
	public int versa(int value) {
		this.disponibilita += value;
		return this.disponibilita;
	}
	
	/**
	 * 
	 * @param value
	 * @return la disponibilità ad operazione avvenuta
	 * 
	 * @throws MyException se la disponibilità è < della richiesta di 
	 * prelievo.
	 * 
	 */
	public int preleva(int value) {
		if (this.disponibilita < value) 
			throw new MyException("Disponiblità: " + this.disponibilita 
					+ " < richiesta prelievo: " + value);
		
		this.disponibilita -= value;
		return this.disponibilita;
	}
	
	public int getDisponibilita() {
		return this.disponibilita;
	}
	
}

public class TestPortafoglioMyException {
    	
	public static void main(String[] args) {
		
		Portafoglio2_0 portLucia = new Portafoglio2_0();
		
		portLucia.versa(400);
		portLucia.preleva(600);
		
	}

}

Eccezioni da gestire obbligatoriamente

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:

package tryexception;

public class PortafoglioException extends Exception {
	
	PortafoglioException(String message) {
		super(message);
	}
}
package tryexception;

class Portafoglio3_0 {
	
	private int disponibilita = 0;
	
	/**
	 * 
	 * @param value
	 * @return la disponibilità ad operazione avvenuta
	 */
	public int versa(int value) {
		this.disponibilita += value;
		return this.disponibilita;
	}
	
	/**
	 * 
	 * @param value
	 * @return la disponibilità ad operazione avvenuta
	 * @throws PortafoglioException se la disponibilità è < della richiesta di 
	 * prelievo.
	 * 
	 */
	public int preleva(int value) throws PortafoglioException {
		if (this.disponibilita < value) 
			throw new PortafoglioException("Disponiblità: " + this.disponibilita 
					+ " < richiesta prelievo: " + value);
		
		this.disponibilita -= value;
		return this.disponibilita;
	}
	
	public int getDisponibilita() {
		return this.disponibilita;
	}
	
}

public class TestPortafoglioWithPortafogliException {
    	
	public static void main(String[] args) {
		
		Portafoglio3_0 portLucia = new Portafoglio3_0();
		
		portLucia.versa(400);
		try {
			portLucia.preleva(600);
		} catch (PortafoglioException e) {
			System.out.println("PortafoglioException: " + e.getMessage());
		}
		
		System.out.println("SONO QUI");
		
	}

}

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

ESERCIZIO

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 .

  1. Verificate costruendo dei casi in cui verificate il lancio delle le due eccezioni.

  2. 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.

Metodo preleva della classe Porfolio

Last updated