Corso Java : Lezione 4 Eccezioni IO Eccezioni • • • • • Che cos'è un'eccezione Come si “catchano” le eccezioni Come si lanciano le eccezioni Eccezioni non controllate Vantaggi nell'uso delle eccezioni Eccezioni Definizione : •Un' eccezione è un evento, che occorre durante l'esecuzione di un programma, che “disturba” il normale flusso di esecuzione delle istruzioni che compongono il programma. • Eccezioni Quando un errore occorre all'interno di un metodo, il metodo crea un oggetto e lo restituisce alla runtime •L'oggetto, chiamato eccezione, contiene informazioni riguardo l'errore, compreso il tipo di errore e lo stato dell'applicazione al momento dell'errore •Creare un oggetto exception e restituirlo alla runtime viene detto “lanciare un'eccezione”. • Eccezioni : call stack Dopo che il metodo ha lanciato l'eccezione, la runtime cerca di trovare un oggetto per gestire l'eccezione stessa: l'insieme dei possibili oggetti per gestire l'eccezione è la lista ordinata dei metodi che sono stati chiamati per arrivare al metodo in cui si è verificata l'eccezione Eccezioni : call stack La runtime cerca nella call stack un metodo che contiene il blocco di istruzioni per gestire l'eccezione ( chiamato exception handler ) L'exception handler scelto viene detto che “catcha l'eccezione” Se non viene trovato un exception handler appropriato la runtime termina Come si “catchano” Un codice java valido deve essere conforme al “Catch or Specify Requirement” ossia, il codice che può lanciare un'eccezione deve essere racchiuso Uno statement try che “catcha” l'eccezione Un metodo che specifica che può lanciare un'eccezione Il codice non conforme non viene compilato I tre tipi di eccezione Checked Exceptions Sono condizioni “eccezionali” che un programma ben scritto è in grado di anticipare e gestire ( es. eccezioni di io ) Tutte le eccezioni sono checked exception tranne Error, RuntimeException e le loro sottoclassi Error Sono normalmente errori con cause esterne all'applicazione ( es. lettura di un file da un disco rotto ) Runtime Exception Sono condizioni di errore interne all'applicazione ma che normalmente non si è in grado di anticipare . Indicano, normalmente, un bug o un errore nella logica dell'applicazione Esempio import java.io.*; import java.util.Vector; public class ListOfNumbers { private Vector vector; private static final int SIZE = 10; public ListOfNumbers () { vector = new Vector(SIZE); for (int i = 0; i < SIZE; i++) { vector.addElement(new Integer(i)); } } public void writeList() { PrintWriter out = new PrintWriter( new FileWriter("OutFile.txt")); // IOException for (int i = 0; i < SIZE; i++) { out.println("Value at: " + i + " = " + vector.elementAt(i)); //ArrayIndexOutOfBoundsException } out.close(); } try catch ( finally ) Il primo passo per construire un exception handler è racchiudere il codice che può lanciare un eccezione dentro un blocco try : try { codice } Per l'esempio precedente : private Vector vector; private static final int SIZE = 10; PrintWriter out = null; try { System.out.println("Entered try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { out.println("Value at: " + i + " = " + vector.elementAt(i)); } try catch ( finally ) Il codice all'interno del blocco try viene gestito da un exception handler. Per costruire l'exception handler associato al blocco try si costruiscono uno o più blocchi catch try { } catch (ExceptionType name) { } catch (ExceptionType name) { } Ogni blocco catch rappresenta un exception handler per il tipo di eccezione che può essere lanciato all'interno del blocco try Ogni tipo di eccezione eredita ( estende ) la classe Throwable try catch ( finally ) Riprendendo l'esempio precedente : try { codice } catch (FileNotFoundException e) { System.err.println("FileNotFoundException: " + e.getMessage()); throw new SampleException(e); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); try catch ( finally ) Il blocco di istruzioni finally viene sempre eseguito all'uscita del blocco try ... anche se c'è una return !!! Serve, normalmente, per scrivere del codice di ripulitura o per assicurarsi che una applicazione sia in un determinato stato all'uscita di un blocco try Esempio ( dal precedente ) finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } Esempio ( scritto bene ) public void writeList() { PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter( new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) out.println("Value at: " + i + " = " + vector.elementAt(i)); } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught " + "ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } A volte non è bene gestire le eccezioni Esistono delle situazioni in cui non è consigliabile gestire le eccezioni : Se si stà scrivendo una libreria non è sempre possibile poter definire a priori lo stato in cui l'applicazione che usa la nostra libreria deve trovarsi dopo un eccezione. In questi casi, invece di racchiudere il codice in un blocco try catch , si definisce il metodo in cui il codice si trova in modo che possa lanciare delle eccezioni : public void writeList() throws IOException, ArrayIndexOutOfBoundsException { Attenzione !! ArrayIndexOutOfBoundsException non è una checked exception : in questo modo la forziamo e la rendiamo checked per il programma che usa la nostra libreria public void writeList() throws IOException { Come si lanciano le eccezioni Per poter gestire un eccezione c'è bisogno che da qualche parte l'eccezione venga “lanciata” Sia che l'eccezione sia lanciata da una libreria che dalla runtime si usa sempre la keyword throw Ogni eccezione eredita da Throwable ed è possibile creare le proprie eccezioni per differenziare il tipo di azione da intraprendere per gestirle Il codice da usare è : throw oggettoCheEreditaDaThrowable; Gli oggetti “lanciabili” sono l'insieme degli oggetti “figli” di throwable ( ereditano direttamente ) e dei discendenti indiretti ( oggetti che ereditano dai “figli” o dai “nipoti” di throwable ) La gerarchia di Throwable Esempio di eccezione custom ( RuntimeException ) /* * File NotificationException.java * Created on May 6, 2005 * Copyright Terma Srl */ package com.terma.webdav.notification; /** * @author <a mailto="[email protected]">Marco Ferretti</a> * @version $Revision: 1.3 $ */ public class NotificationException extends RuntimeException{ private static final long serialVersionUID = -9155747208083372937L; public NotificationException ( String message ){ super( message ); } } Esempio di eccezione custom ( Exception ) /* * File ResourceException.java * Created on Apr 30, 2005 * Copyright Terma Srl */ package com.terma.webdav.notification; /** * @author <a mailto="[email protected]">Marco Ferretti</a> * @version $Revision: 1.3 $ */ public class ResourceException extends Exception{ private static final long serialVersionUID = 1L; public ResourceException ( String message ) { super( message ); } } Come si lanciano le eccezioni Spesso i programmi reagiscono alle eccezioni lanciando altre eccezioni questo tipo di eccezione viene chiamata “chained exception” try { } catch (IOException e) { throw new LaMiaEccezione("Un'altra eccezione IOException", e); } Throwable mette a disposizione alcuni metodi e costruttori per poter gestire questi comportamenti : Throwable getCause() Throwable initCause(Throwable) Throwable(String, Throwable) Throwable(Throwable) Vantaggi dall'uso delle eccezioni Separazione del codice di gestione dell'errore dal codice “regolare” Propagazione dell'errore nello stack Raggruppamento e differenziazione dei tipi di errore IO ( Input Output ) Streams Gli streams sono un’astrazione : possono rappresentare qualunque fonte o destinazione per un flusso di dati ( file, terminali, dischi, programmi … ) Supportano molti tipi di dato ( dati primitivi, array, oggetti … ) Alcuni sono trasparenti, altri manipolano i dati Per i programmi SONO UNA SEQUENZA DI DATI : in input in output IO ( Input Output ) • • • • • • Byte stream Char stream Buffered streams Stream di dati Object stream File-IO IO : Byte Stream • • • • Io di 8 bytes Ereditano da InputStream e OutputStream Le classi specializzate per i files sono FileInputStream e FileOutputStream Esempio : import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyBytes { public static void main(String[] args) throws IOException { FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("xanadu.txt"); out = new FileOutputStream("outagain.txt"); int c; while ((c = in.read()) != -1) { out.write(c); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } } IO : Byte Stream • • • La maggior parte del tempo è impiegata nel loop Viene letto e scritto un byte alla volta read() ritorna un intero invece di un byte : posso usare -1 per rappresentare la fine del file IO : Byte Stream • Attenzione : • • Chiudere sempre gli streams quando non se ne fà piu uso CopyBytes è una procedura che lavora a basso livello ( opera a livello di byte ) : nel caso in esame è consigliabile usare stream di caratteri IO : Char Stream • • • • • In java i caratteri registrati in memoria usando il set Unicode : vengono automaticamente tradotti nel set di default della macchina Le operazioni di IO con i char streams non sono diversi, da un punto di vista di programmazione, da quelle con I byte stream Un programma che opera con i char stream invece dei byte stream converte automaticamente i caratteri nel set locale : i programmi sono già pronti per l’internazionalizzazione !! Tutti i char stream ereditano da Reader e Writer Le classi specializzate per i files sono FileReader e FileWriter IO : Char Stream • Esempio : import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyCharacters { public static void main(String[] args) throws IOException { FileReader inputStream = null; FileWriter outputStream = null; try { inputStream = new FileReader("xanadu.txt"); outputStream = new FileWriter("characteroutput.txt"); int c; while ((c = inputStream.read()) != -1) { outputStream.write(c); } } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } } IO : Char Stream • La variabile intera in CopyBytes contiene dei byte ( 8 bit ) • La variabile intera in CopyChars contiene dei char ( 16 bit ) IO : Char Stream • La variabile intera in CopyBytes contiene dei byte ( 8 bit ) La variabile intera in CopyChars contiene dei char ( 16 bit ) • Spesso gli stream di caratteri sono dei “wrappers” per gli stream di bytes • • • Ad esempio FileReader usa FileInputStream per le operazioni di IO di “basso livello” Esistono due “ponti” tra i byte ed i caratteri : InputStreamReader e OutputStreamWriter : quando non ci sono dei wrappers che fanno al caso nostro utilizzeremo queste classi per costruire i nostri “io stream class” IO : Char Stream • • • Normalmente le operazioni di IO di testo si fanno su entità più grandi dei caratteri : l’unità di misura più comune è la linea. La linea è una stringa di caratteri con un terminatore di stringa alla fine Il terminatore di stringa può essere : • • • • • carriage return + line feed (“\r\n”) carriage return (“\r”) fine feed(“\n”) L’uso di classi specializzate nella lettura e scrittura di files “a linee” permette di minimizzare l’accesso al disco e velocizzare l’esecuzione del programma. Le classi specializzate nell’IO su file “a linee” sono BufferedReader e PrintWriter IO : Char Stream • Esempio import java.io.FileReader; import java.io.FileWriter; import java.io.BufferedReader; import java.io.PrintWriter; import java.io.IOException; public class CopyLines { public static void main(String[] args) throws IOException { BufferedReader inputStream = null; PrintWriter outputStream = null; try { inputStream = new BufferedReader(new FileReader("xanadu.txt")); outputStream = new PrintWriter(new FileWriter("characteroutput.txt")); String l; while ((l = inputStream.readLine()) != null) { outputStream.println(l); } } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } } IO : Char Stream • • • readLine ritorna una stringa di caratteri leggendo il file fino al terminatore di stringa println scrive la stringa sullo stream appendendo il terminatore di stringa Il terminatore di stringa viene selezionato automaticamente : potrebbe essere diverso da sistema operativo a sistema operativo. IO : Buffered Stream • • • • Tutte le operazioni di IO viste finora usano stream “unbuffered” : tutte le operazioni di IO sono gestite dal sistema operativo sottostante la virtual machine Questo porta ad avere dei programmi poco efficenti perchè ogni richiesta di dato comporta un accesso a disco o una lettura dalla rete o, comunque, un aoperazione che è relativamente dispendiosa Per ovviare a questo problema, la API di java implementa degli stream bufferizzati : le gli accessi a disco avvengono, in lettura, solo quando il buffer è vuoto, in scrittura solo quando il buffer è pieno . I programmi che usano stream non bufferizzati possono essere facilmente convertiti usando I wrappers : inputStream = new BufferedReader(new FileReader("xanadu.txt")); outputStream = new BufferedWriter(new FileWriter("characteroutput.txt")); • Esistono quattro stream bufferizzati : • BufferedInputStream e BufferedOutputStream per i bytes • BufferedReader e BufferedWriter per i char • E’ sensato svuotare I buffer in punti critici : flushing IO : Data Stream • • • • • • • • I data stream supportano operazioni di binarie IO su I tipi di dato primitivi e le String : vengono usati per scrivere dei “record” Tutti i data stream implementano l’interfaccia DataInput o DataOutput Le due classi più utilizzate sono DataInputStream e DataOutputStream Questi stream sono dei wrappers per degli stream di byte esistenti : devono essere creati prima dei data stream Definiscono metodi specializzati per la scrittura e la lettura dei dati a seconda del tipo Le letture devono essere eseguite nello stesso ordine in cui vengono fatte le scritture La condizione di fine stream viene identificata tramite EOFException Usano il floating point per scrivere e leggere i valori monetari . In generale i float non sono appropriati per rappresentare valori precisi; in particolare non sono appropriati per rappresentare delle frazioni di decimale per cui non esistono delle rappresentazioni binarie ( es. 0.1 ) IO : Data Stream import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.EOFException; DataInputStream in = null; double total = 0.0; try { in = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile))); public class TestDataStream { static final String dataFile = "invoicedata"; double price; int unit; String desc; static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 }; static final int[] units = { 12, 8, 13, 29, 50 }; static final String[] descs = { "Java T-shirt", "Java Mug", "Duke Juggling Dolls", "Java Pin", "Java Key Chain" }; public static void main(String[] args) throws IOException { DataOutputStream out = null; try { out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile))); for (int i = 0; i < prices.length; i ++) { out.writeDouble(prices[i]); out.writeInt(units[i]); out.writeUTF(descs[i]); } } finally { out.close(); } } try { while (true) { price = in.readDouble(); unit = in.readInt(); desc = in.readUTF(); System.out.println("You ordered "+unit+" units of "+desc+" at $" + price); total += unit * price; } } catch (EOFException e) { } System.out.println("For a TOTAL of: "+ total); } finally { in.close(); } } IO : Object Stream • • • • • Funzionano come i DataStream Lavorano con tutti le classi che implementano Serializable Le classi sono ObjectInputStream e ObjectOutputStream che implementano le interfacce ObjectInput e ObjectOutput . Queste a loro volta sono delle “sottointerfacce” di DataInput e DataOutput Il metodo generico di lettura è readObject() . In uno stream è possibile scrivere solo una volta l’istanza di una classe Esempio Object a = new Object () ; Object b = a; ObjectOutputStream out = …. out.writeObject(a); out.writeObject(b); • L’oggetto viene scritto una volta mentre vengono scritte due volte il riferimento all’oggetto • Se un oggetto viene scritto su due stream diversi allora dopo la lettura dei due stream avrò due istanze separate dell’oggetto. IO : Object Stream • Il metodo writeObject è in grado di attraversare i riferimenti interni di un oggetto ad altri oggetti … così come readObject : In questo esempio l’oggetto a fà riferimento ( contiene istanze di ) c e b; b fà riferimento a d ed e ; con una sola writeObject ed una readObject scrivo e loggo dallo stream 5 oggetti File IO Gli stream non supportano tutte le operazioni necessarie per lavorare con i files di un disco, per questo la java API mette a disposizione la classe File • L’istanza della classe File rappresenta il filename non il file vero e proprio. • La classe File prende in input una stringa che rappresenta il nome del file : questa stringa non può essere modificata durante la vita dell’istanza File a = new File(“pippo.txt”) • Metodo Su Windows Su Unix a.toString() pippo.txt pippo.txt a.getName() pippo.txt pippo.txt a.getParent() NULL NULL a.getAbsolutePath() c:\java\examples\pippo.txt /home/cafe/java/examples/pippo.txt a.getCanonicalPath() c:\java\examples\pippo.txt /home/cafe/java/examples/pippo.txt File IO • File b = new File(“..” + File.separator + “examples” + File.separator + “pippo.txt”); Metodo Su Windows Su Unix a.toString() ..\examples\pippo.txt ../examples/pippo.txt a.getName() pippo.txt pippo.txt a.getParent() ..\examples\ ../examples/ a.getAbsolutePath() c:\java\examples\.. examples\pippo.txt /home/cafe/java/examples/../examples/pi ppo.txt a.getCanonicalPath() c:\java\examples\pippo.txt /home/cafe/java/examples/pippo.txt File IO • • E’ utile ricordare che nell’esempio precedente il metodo compareTo() non restituirebbe vero prechè i due oggetti sono stati creati a partire da nomi diversi anche se puntano allo stesso file Se l’oggetto File rappresenta un file esistente nel filesystem, questo può essere usato per manipolare il file : • • • • • L’oggetto File può essere usato anche per manipolare directory : • • • • • delete : cancella il file deleteOnExit : cancella il file quando la VM termina setLastModified : setta la data di ultima modifica renameTo : rinomina il file mkdir : crea la directory mkdirs : crea la directory dopo aver verificato/creato l’albero list : lista dei files/directory all’interno della directory ; ritora un array di String listFiles : come list ma ritorna un’array di File Esistono alcuni metodi statici utili per la manipolazione del filesystem • • createTempFile : crea un file con un nome univoco e ritorna un oggetto File listRoots : ritorna la lista delle radici del filesystem File IO : random access • • • • • • • Tutto quello che abbiamo visto finora serviva per avere un accesso sequenziale ai files. A volte ( es. Zip-files ) è utile poter accedere in maniera non sequenziale al contenuto di un file La classe RandomAccessFile in java.io implementa alcuni metodi che sono utili a questo tipo di accesso. Implementa sia DataInputStream che DataOutputStream e può essere usata sia per leggere che per scrivere Quando si crea un oggetto RandomAccessFile si deve specificare se si vuole solo leggere il file o anche scriverlo (“r” o “rw”) Una volta che il file è stato aperto si possono usare i metodi read e write di DataInputStream e DataOutputStream per leggere e scrivere Supporta la nozione di filepointer: all’apertura è in posizione 0 • • • int skipBytes ( int ) : salta int bytes void seek(long) : posiziona il filepointer al byte precedente il valore passato long getFilePointer () : ritorna la posizione corrente