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