Gestione delle Eccezioni C. Horstmann Fondamenti di programmazione e Java 2 3^ edizione Apogeo trad. Nicola Fanizzi corso di Programmazione, CdS: Informatica TPS Dip. di Informatica, Università degli studi di Bari Obiettivi Imparare a lanciare eccezioni Essere capaci di progettare le proprie classi di eccezioni Capire la differenza tra eccezioni controllate e incontrollate Imparare a catturare le eccezioni Sapere quando e dove catturare un'eccezione 2 Gestione Errori 3 Approccio tradizionale: il metodo restituisce un codice d'errore Problema: dimenticare di controllare il codice-errore La notifica di errore potrebbe passare inosservata Problema: il metodo chiamante potrebbe non essere in grado di far niente in caso di errore Anche il programma deve riportare un errore e delegare il proprio chiamante ad occuparsene Molte chiamate di metodo necessiterebbero di un controllo Gestione Errori 4 Invece di programmare pensando ai casi di successo: x.doSomething() si dovrebbe sempre programmare stando attenti ai possibili errori: if (!x.doSomething()) return false; Lanciare Eccezioni 5 Eccezioni: Non si possono ignorare Inviate direttamente ad un gestore (exception handler)–e non semplicemente al chiamante del metodo che ha causato il problema Lancia un'eccezione (è un oggetto) per segnalare una condizione eccezionale Esempio: IllegalArgumentException: Valore illegale per il parametro IllegalArgumentException exception = new IllegalArgumentException("Amount exceeds balance"); lancia exception; Lanciare Eccezioni 6 Non occorre immagazzinare l'oggetto eccezione in una variabile: throw new IllegalArgumentException("Amount exceeds balance"); Quando si lancia un'eccezione, il metodo termina immediatamente L'esecuzione continua con un gestore delle eccezioni Esempio public class BankAccount { public void withdraw(double amount) { if (amount > balance) { IllegalArgumentException exception = new IllegalArgumentException("Amount exceeds balance"); throw exception; } balance = balance - amount; } . . . } 7 Gerarchia delle 8 Classi di Eccezioni Sintassi 15.1: 9 Lanciare un'Eccezione throw exceptionObject; Esempio: throw new IllegalArgumentException(); Scopo: lanciare un'eccezione e trasferire il controllo al gestore per questo tipo di eccezione Eccezioni 10 Controllate e Non Controllate Due tipi di eccezioni: Controllate (checked) Il compilatore controlla che non vengano ignorate Dovute a cause esterne che il programmatore non saprebbe prevenire La maggioranza di essere accade quando si tratta l'I/O Ad esempio: IOException Eccezioni 11 Controllate e Non Controllate Due tipi di eccezioni: Non controllate: Estendono le classi RuntimeException o Error Sono da imputare al programmatore Esempi di eccezioni di runtime: NumberFormatException IllegalArgumentException NullPointerException Esempio di errore: OutOfMemoryError Eccezioni 12 Controllate e Non Controllate Le categorie non sono precise: Scanner.nextInt lancia l'eccezione non controllata InputMismatchException Il programmatore non può prevenire l'immissione di input non corretto da parte degli utenti Questa scelta rende la classe facile da usare per programmatori principianti Si ha a che fare con eccezioni controllate quando si programma con file e stream Eccezioni 13 Controllate e Non Controllate Ad esempio, si usi uno Scanner per leggere un file String filename = . . .; FileReader reader = new FileReader(filename); Scanner in = new Scanner(reader); Ma il costruttore di FileReader può lanciare una FileNotFoundException Eccezioni 14 Controllate e Non Controllate Due scelte: Gestire l'eccezione Dire al compilatore che si vuole che il metodo termini quando si abbia l'eccezione Si usa lo specificatore throws affinché il metodo possa lanciare un'eccezione controllata public void read(String filename) throws FileNotFoundException { FileReader reader = new FileReader(filename); Scanner in = new Scanner(reader); . . . } Eccezioni 15 Controllate e Non Controllate Per eccezioni multiple: public void read(String filename) throws IOException, ClassNotFoundException Tenere presente la gerarchia di ereditarietà: Meglio dichiarare l'eccezione piuttosto che gestirla in maniera sprovveduta Sintassi 15.2: 16 Specifica delle Eccezioni accessSpecifier returnType methodName(parameterType parameterName, . . .) throws ExceptionClass, ExceptionClass, . . . Esempio: public void read(BufferedReader in) throws IOException Scopo: indicare le eccezioni controllate che questo metodo possa lanciare Catturare Eccezioni 17 Installare un gestore di eccezioni con l'istruzione try/catch: Il blocco try contiene istruzioni che possono causare un'eccezione La clausola catch introduce il gestore per un tipo di eccezione Catturare Eccezioni Esempio: try { String filename = . . .; FileReader reader = new FileReader(filename); Scanner in = new Scanner(reader); String input = in.next(); int value = Integer.parseInt(input); . . . } catch (IOException exception) { exception.printStackTrace(); } catch (NumberFormatException exception) { System.out.println("Input was not a number"); } 18 Catturare Eccezioni 19 Le istruzioni in un blocco try vengono eseguite Se non si ha nessuna eccezione, le clausole catch sono saltate Se si ha un'eccezione di uno dei tipi gestiti, l'esecuzione salta al corrispondente blocco catch Se si ha un'eccezione di altro tipo, essa viene rilanciata fino a quando non venga catturata da un altro blocco try Catturare Eccezioni catch block 20 (IOException exception) exception contiene il riferimento all'oggetto eccezione che era stato lanciato La clausola catch può analizzare l'oggetto per scoprire altri dettagli exception.printStackTrace(): stampa della catena di chiamate di metodi che hanno portato ad exception Sintassi 15.3: 21 Blocco try Generico try { statement statement . . . } catch (ExceptionClass exceptionObject) { statement statement . . . } catch (ExceptionClass exceptionObject) { statement statement . . . } . . . Sintassi 15.3: 22 Blocco try Generico Example: try { System.out.println("How old are you?"); int age = in.nextInt(); System.out.println("Next year, you'll be " + (age + 1)); } catch (InputMismatchException exception) { exception.printStackTrace(); } Scopo: eseguire una o più istruzioni che possono generare eccezioni. Se si ha un'eccezione tra quelle delle clausole catch si esegue la prima di esse. Se non si hanno eccezioni, o l'eccezione occorsa non corrisponde con quelle delle catch, allora si saltano tutte le clausole catch La clausola finally Un'eccezione causa la terminazione del metodo 23 corrente Pericolo: può far saltare del codice che è essenziale Esempio: reader = new FileReader(filename); Scanner in = new Scanner(reader); readData(in); reader.close(); // si potrebbe non arrivare mai qui La clausola finally Occorre eseguire reader.close() 24 anche se si ha l'eccezione Si usa la clausola finally per il codice che deve essere eseguito "a prescindere" FileReader reader = new FileReader(filename); try { Scanner in = new Scanner(reader); readData(in); } finally { reader.close(); // se si ha un'eccezione, la clausola // finally viene sempre eseguita // prima del passaggio al gestore } La clausola finally 25 Eseguita quando si esce dal blocco try in uno dei tre modi: Dopo l'ultima istruzione del blocco try Dopo l'ultima istruzione della clausola catch se il blocco try ha catturato un'eccezione Quando un'eccezione è stata lanciata dal blocco try e non è stata catturata Raccomandazione: tenere distinte le clausole catch e finally dello stesso blocco try Sintassi 15.4: 26 La clausola finally try { statement statement . . . } finally { statement statement . . . } Sintassi 15.4: 27 La clausola finally Esempio: FileReader reader = new FileReader(filename); try { readData(reader); } finally { reader.close(); } Scopo: assicurare che le istruzioni nella clausola finally siano eseguite a prescindere dal fatto che nel blocco try si lancino eccezioni Progettare i Propri 28 Tipi d'Eccezione Si possono progettare propri tipi di eccezioni– sottoclassi di Exception o RuntimeException if (amount > balance) { throw new InsufficientFundsException( "withdrawal of " + amount + " exceeds balance of “ + balance); } La si può rendere eccezione non controllata – il programmatore avrebbe potuto evitarla chiamando prima getBalance Progettare i Propri 29 Tipi d'Eccezione Si può estende RuntimeException o una delle sue sottoclassi Fornire due costruttori Costruttore di default Costruttore che accetta una stringa di messaggio che descriva la ragione dell'eccezione Progettare i Propri 30 Tipi d'Eccezione public class InsufficientFundsException extends RuntimeException { public InsufficientFundsException() {} } public InsufficientFundsException(String message) { super(message); } Programma Completo 31 Programma Chiedere all'utente un nome di file Ci si aspetta che il file contenga dei valori La prima riga del file contiene il numero totale di valori Le rimanenti contengono i dati Tipico file di input: 3 1.45 -2.1 0.05 Programma Completo 32 Cosa può andar male ? Il file potrebbe non esistere Il file potrebbe contenere dati in formato sbagliato Chi può accorgersi degli errori ? Il costruttore FileReader lancerà una eccezione quando il file non esiste I metodi che elaborano l'input hanno bisogno di lanciare una eccezione se trovano un errore nel formato dei dati Programma Completo 33 Quali eccezioni si possono lanciare? FileNotFoundException può essere lanciata dal costruttore di FileReader IOException può essere lanciata dal metodo close di FileReader BadDataException, una classe ad hoc d'eccezione controllata Programma Completo 34 Chi può rimediare agli errori riportati dalle eccezioni? Solo il metodo main del programma DataSetTester interagisce con l'utente Cattura eccezioni Stampa messaggi d'errore appropriati Dà all'utente un'altra possibilità di inserimento di un nome di file corretto DataSetTester.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: import import import java.io.FileNotFoundException; java.io.IOException; java.util.Scanner; public class DataSetTester { public static void main(String[] args) { Scanner in = new Scanner(System.in); DataSetReader reader = new DataSetReader(); boolean done = false; while (!done) { try { 35 DataSetTester.java 17: "); 18: 19: 20: 21: 22: 23: 24: 25: 36 System.out.println("Please enter the file name: String filename = in.next(); double[] data = reader.readFile(filename); double sum = 0; for (double d : data) sum = sum + d; System.out.println("The sum is " + sum); done = true; } // blocco try DataSetTester.java 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 37 catch (FileNotFoundException exception) { System.out.println("File not found."); } catch (BadDataException exception) { System.out.println ("Bad data: " + exception.getMessage()); } catch (IOException exception) { exception.printStackTrace(); } } // while } // main } // classe Il metodo readFile della 38 classe DataSetTester Costruisce l'oggetto Scanner Chiama il metodo readData Si disinteressa completamente di qualunque eccezione Il metodo readFile della 39 classe DataSetTester Se c'è un problema con il file di input, passa semplicemente l'eccezione al chiamante public double[] readFile(String filename) throws IOException, BadDataException // FileNotFoundException è una IOException { FileReader reader = new FileReader(filename); try { Scanner in = new Scanner(reader); readData(in); } Il metodo readFile della 40 classe DataSetTester } finally { reader.close(); } return data; Il metodo readData della 41 classe DataSetTester Legge il numero di valori Costruisce un array Chiama readValue per ogni valore Controlla due potenziali errori: Il file potrebbe non cominciare con un intero Il file potrebbe avere ancora dati dopo la lettura di quelli indicati nella prima riga Non fa nessun tentativo di cattura di eccezioni Il metodo readData della 42 classe DataSetTester private void readData(Scanner in) throws BadDataException { if (!in.hasNextInt()) throw new BadDataException("Length expected"); int numberOfValues = in.nextInt(); data = new double[numberOfValues]; for (int i = 0; i < numberOfValues; i++) readValue(in, i); } if (in.hasNext()) throw new BadDataException("End of file expected"); Il metodo readValue della 43 classe DataSetTester private void readValue(Scanner in, int i) throws BadDataException { if (!in.hasNextDouble()) throw new BadDataException("Data value expected"); data[i] = in.nextDouble(); } Scenario 44 1.DataSetTester.main chiama DataSetReader.readFile 2.readValue chiama readData 3.readData chiama readValue 4.readValue non trova il valore che si attendeva e lancia BadDataException 5.readValue non ha un gestore per l'eccezione e termina Scenario 45 6. readData non ha un gestore per le eccezioni e termina 7. readFile non ha un gestore per le eccezioni e termina dopo aver eseguito la clausola finally 8. DataSetTester.main ha un gestore per BadDataException; il gestore stampa un messaggio e si dà all'utente un'altra possibilità di immissione del nome del file DataSetReader.java 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: import java.io.FileReader; import java.io.IOException; import java.util.Scanner; /** legge un insieme di dati da file. // il file ha il formato numberOfValues valore1 valore2 . . . */ public class DataSetReader { 46 DataSetReader.java 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 47 /** legge un insieme di dati. @param filename il nome del file contenente i dati @return i dati del file */ public double[] readFile(String filename) throws IOException, BadDataException { FileReader reader = new FileReader(filename); try { Scanner in = new Scanner(reader); readData(in); } finally { reader.close(); } return data; } DataSetReader.java 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 48 /** legge tutti i dati. @param in lo scanner che scandisce i dati */ private void readData(Scanner in) throws BadDataException { if (!in.hasNextInt()) throw new BadDataException("Length expected"); int numberOfValues = in.nextInt(); data = new double[numberOfValues]; for (int i = 0; i < numberOfValues; i++) readValue(in, i); } if (in.hasNext()) throw new BadDataException("End of file expected"); DataSetReader.java 53: 54: 55: 56: 57: 58: 49 /** legge il valore di un dato . @param in lo scanner che scandisce i dati @param i la posizione del valore da leggere */ private void readValue(Scanner in, int i) throws BadDataException { if (!in.hasNextDouble()) throw new BadDataException("Data value expected"); data[i] = in.nextDouble(); } 59: 60: 61: 62: 63: 64: 65: private double[] data; 66: } // classe