1. Gerarchia delle eccezioni di I/O
In Java, lavorando con i file e altre risorse esterne, quasi sempre ci si imbatte nelle cosiddette eccezioni di input/output. Sono oggetti lanciati (throw) quando qualcosa va storto in lettura o scrittura dei dati. Per esempio, se il file non si trova, non ci sono i permessi oppure il disco all’improvviso «si è stancato».
I protagonisti della nostra lezione
In Java esiste un’intera gerarchia di tali eccezioni. Ecco le principali:
- IOException — la classe di base per tutti gli errori di input/output. Se qualcosa va storto con file, flussi o rete — quasi sempre è «colpevole» lei (o una delle sue numerose sottoclassi).
- FileNotFoundException — «figlio» di IOException, che appare quando si tenta di aprire un file inesistente o il percorso è indicato in modo errato.
Altri derivati:
- EOFException — anch’essa «figlio» diretto di IOException. Indica che durante la lettura si è raggiunta inaspettatamente la fine del file.
- MalformedInputException — «nipote»: estende CharacterCodingException, che a sua volta estende IOException. Questo errore si verifica se il file non può essere interpretato correttamente con la codifica specificata (ad esempio, ci si aspettava "UTF-8", ma è arrivata una sequenza danneggiata).
- Ci sono anche SocketException, ZipException e altri «parenti» specializzati, ciascuno con il proprio ambito di responsabilità. Più si scende nella gerarchia, più la situazione è ristretta e specifica.
Piccolo schema semplificato:
java.lang.Exception
└── java.io.IOException
├── java.io.FileNotFoundException
├── java.io.EOFException
├── java.nio.charset.MalformedInputException
└── ... (altri)
Fatto interessante:
In Java quasi tutte le operazioni con i file richiedono di dichiarare o gestire IOException — sono i cosiddetti checked exceptions. Il compilatore non vi lascerà dimenticare la gestione degli errori!
2. Quando e perché si verificano queste eccezioni
Apertura di un file inesistente
Il caso più comune: tentate di aprire un file, ma non c’è. È come arrivare alla fermata e scoprire che l’autobus non esiste nemmeno in orario.
FileInputStream fis = new FileInputStream("abracadabra.txt"); // Boom! FileNotFoundException
Tentativo di scrivere su un file senza permessi
Se provate a scrivere un file in una cartella in cui non avete i permessi, otterrete FileNotFoundException o IOException (a seconda del caso).
FileOutputStream fos = new FileOutputStream("/system/secret.txt"); // Boom! FileNotFoundException o IOException
Errori di lettura/scrittura a causa di supporto danneggiato
A volte il file sembra esserci, ma il disco è danneggiato, il file è bloccato da un altro programma o la corrente si è spenta all’improvviso — in tal caso riceverete una IOException con messaggi diversi.
Altre cause
- Il file è di sola lettura, ma state tentando di scrivere.
- Il percorso del file è troppo lungo o contiene caratteri non validi.
- Il file è utilizzato da un altro processo.
- Il disco è pieno.
- Il file viene eliminato da un altro processo tra il controllo e l’uso.
3. Gestione con try-catch
Ogni volta che lavorate con file, flussi, rete — usate try-catch. È come un airbag: se qualcosa va storto, il programma non si bloccherà, ma potrà reagire correttamente.
Come catturare correttamente le eccezioni?
In Java bisogna catturare prima le eccezioni più specifiche e poi quelle generiche. Se mettete per primo un catch generale (IOException e), allora quelli più ristretti, per esempio FileNotFoundException, non verranno eseguiti — il controllo non ci arriverà.
Struttura corretta:
try {
// Lavoro con il file
} catch (FileNotFoundException e) {
// Gestione della situazione "file non trovato"
} catch (IOException e) {
// Gestione di altri errori di I/O
}
Perché?
Perché FileNotFoundException è un caso particolare di IOException. Se catturate prima il caso generale, quello particolare non «arriverà» al suo catch.
Esempio di codice: gestione degli errori all'apertura di un file
import java.io.*;
public class FileReaderExample {
public static void main(String[] args) {
String filename = "notes.txt";
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line = reader.readLine();
System.out.println("Prima riga del file: " + line);
reader.close();
} catch (FileNotFoundException e) {
System.out.println("File non trovato: " + filename);
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
}
}
Nota bene:
Anche se avete verificato che il file esiste, lasciate sempre try-catch — il file può scomparire in qualsiasi momento (ad esempio, un altro processo potrebbe eliminarlo).
4. Pratica: scriviamo codice con gestione degli errori
Creiamo esempi semplici e facciamo in modo che reagiscano correttamente all’assenza del file e ad altri errori.
Passo 1: proviamo ad aprire un file inesistente
import java.io.*;
public class NotesApp {
public static void main(String[] args) {
String filename = "my_notes.txt";
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line;
System.out.println("Le tue note:");
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (FileNotFoundException e) {
System.out.println("Ops! Il file delle note non è stato trovato: " + filename);
System.out.println("Suggerimento: crea il file o verifica il nome.");
} catch (IOException e) {
System.out.println("Si è verificato un errore durante la lettura del file: " + e.getMessage());
}
}
}
Passo 2: aggiungiamo la gestione per la scrittura su file
import java.io.*;
public class NotesWriter {
public static void main(String[] args) {
String filename = "my_notes.txt";
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true)); // append: true
writer.write("Nuova nota!\n");
writer.close();
System.out.println("Nota aggiunta con successo!");
} catch (IOException e) {
System.out.println("Errore durante la scrittura su file: " + e.getMessage());
}
}
}
Passo 3: esempio universale con logging degli errori
import java.io.*;
public class SafeFileCopier {
public static void main(String[] args) {
String source = "source.txt";
String target = "target.txt";
try {
BufferedReader reader = new BufferedReader(new FileReader(source));
BufferedWriter writer = new BufferedWriter(new FileWriter(target));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
reader.close();
writer.close();
System.out.println("File copiato con successo!");
} catch (FileNotFoundException e) {
System.out.println("File non trovato: " + e.getMessage());
} catch (IOException e) {
System.out.println("Errore di I/O: " + e.getMessage());
}
}
}
Consiglio:
Nelle applicazioni reali conviene registrare gli errori non solo a schermo, ma anche su un file o in un registro (log), così da poter capire in seguito che cosa sia andato storto.
5. Tabella: principali eccezioni di I/O
| Eccezione | Quando si verifica? | Come gestirla? |
|---|---|---|
|
File non trovato, percorso inesistente, mancano i permessi | Informare l’utente, verificare percorso/permessi, creare il file se necessario |
|
Fine del file inattesa durante la lettura | Segnalare danneggiamento/incompletezza, provare a ripristinare parzialmente i dati |
|
Errore di I/O generico (disco, permessi, blocco) | Controllare i dettagli, terminare l’operazione correttamente, riprovare se possibile |
|
Codifica o struttura del file non corretta | Segnalare il danneggiamento, provare un’altra codifica/sorgente |
6. Errori tipici nella gestione delle eccezioni di I/O
Errore n. 1: catturare solo il generico Exception. È molto allettante scrivere semplicemente catch (Exception e), ma così non riuscirete a distinguere che cosa sia andato storto. Meglio catturare prima le eccezioni specifiche (FileNotFoundException) e poi — il generico IOException.
Errore n. 2: non chiudere i flussi in caso di errori. Se avete aperto un file e poi si verifica un’eccezione, il flusso potrebbe restare aperto. Usate try-with-resources o chiudete le risorse in finally.
Errore n. 3: ignorare i messaggi delle eccezioni. Non limitatevi a stampare «Errore!», ma mostrate i dettagli: e.getMessage(). Questo aiuta a capire più rapidamente che cosa è andato storto.
Errore n. 4: non gestire FileNotFoundException in scrittura. Molti pensano che FileNotFoundException riguardi solo la lettura. In realtà può emergere anche in scrittura (percorso non corretto, nessun permesso di creare il file, ecc.).
Errore n. 5: non verificare i permessi di accesso. Se il programma viene avviato con permessi limitati, molte operazioni sui file possono fallire. Consideratelo sempre e informate l’utente.
GO TO FULL VERSION