1. Le eccezioni come parte dell'API
Perché le eccezioni fanno parte del «contratto» del metodo?
Quando scrivi un metodo, definisci non solo i parametri e il valore di ritorno, ma anche quali eccezioni può lanciare. Questo fa parte del «contratto» tra il tuo metodo e chi lo utilizzerà. Se il metodo può lanciare un'eccezione, chi lo chiama deve saperlo — per gestire correttamente l'errore (o almeno non stupirsi quando il programma termina improvvisamente con un errore).
Esempio:
public void readFile(String filename) throws IOException {
// ... lettura del file
}
Qui è esplicitato che il metodo può lanciare IOException. È un segnale per l'utente del metodo: «Sii pronto a gestire l'errore di lettura del file!»
Documentare le eccezioni: tag @throws in Javadoc
Per fare in modo che altri sviluppatori capiscano quali eccezioni può lanciare il tuo metodo, usa il tag @throws (o @exception) in Javadoc.
Esempio:
/**
* Legge il contenuto di un file.
*
* @param filename nome del file
* @return contenuto del file come stringa
* @throws IOException se si verifica un errore di lettura del file
*/
public String readFile(String filename) throws IOException {
// ...
}
Perché è utile?
- Aiuta altri sviluppatori a capire quali errori devono essere gestiti.
- Le IDE e i generatori di documentazione (ad esempio, Javadoc) mostrano queste eccezioni direttamente nei suggerimenti.
- Aumenta l'affidabilità e la prevedibilità del codice.
Eccezioni checked e unchecked nell'API
Le eccezioni checked (sottoclassi di Exception, ma non di RuntimeException) sono parte del contratto del metodo. Devono essere gestite oppure dichiarate esplicitamente (throws).
Le eccezioni unchecked (RuntimeException e sottoclassi) in genere segnalano errori di programmazione (ad esempio, NullPointerException, IllegalArgumentException). Non è obbligatorio indicarle nella firma, ma se il tuo metodo può lanciare un errore del genere (ad esempio per argomenti non validi), è comunque opportuno descriverlo nel Javadoc.
Esempio:
/**
* Divide a per b.
* @param a dividendo
* @param b divisore
* @return risultato della divisione
* @throws IllegalArgumentException se b == 0
*/
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("Il divisore non può essere zero");
return a / b;
}
Eccezioni e progettazione dell'API
- Pensa all'utente del tuo metodo: quali errori può e deve gestire? Quali sono bug e quali situazioni «operative»?
- Non abusare delle eccezioni checked: se l'errore è un bug (ad esempio, argomento non valido), è meglio lanciare un'eccezione unchecked.
- Documenta tutte le eccezioni che possono essere propagate «verso l'alto».
2. Costrutto try-with-resources
Problema: come chiudere in modo sicuro le risorse?
In molti casi si lavora con risorse che devono essere necessariamente chiuse dopo l'uso: file, connessioni di rete, database, ecc. Se ci si dimentica di chiudere una risorsa, si possono avere perdite di memoria, lock su file o altri inconvenienti.
Prima:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
// ...
} catch (IOException e) {
// gestione dell'errore
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// gestione dell'errore in chiusura
}
}
}
Molto codice, facile sbagliare, si può dimenticare di chiudere la risorsa.
Soluzione: try-with-resources
Da Java 7 esiste il costrutto try-with-resources, che chiude automaticamente tutte le risorse, anche se all'interno del blocco si verifica un'eccezione.
Sintassi:
try (ResourceType resource = new ResourceType(...)) {
// lavoro con la risorsa
} catch (ExceptionType e) {
// gestione dell'errore
}
Esempio:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
// reader.close() verrà chiamato automaticamente!
Come funziona?
- Nelle parentesi dopo try si dichiarano le risorse da chiudere.
- Dopo l'uscita dal blocco try (anche se si verifica un'eccezione!) per ogni risorsa viene chiamato il metodo close().
- Funziona solo per risorse che implementano l'interfaccia AutoCloseable (o la sua superinterfaccia Closeable).
Interfaccia AutoCloseable:
public interface AutoCloseable {
void close() throws Exception;
}
Tutte le risorse standard di Java (file, stream, connessioni al DB) implementano questa interfaccia.
Si possono dichiarare più risorse
try (
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
Entrambe le risorse verranno chiuse automaticamente, anche in caso di eccezione.
Vantaggi di try-with-resources
- Sicurezza: le risorse vengono sempre chiuse, anche in caso di errori.
- Concisione: meno codice, meno possibilità di sbagliare.
- Leggibilità: è chiaro quali risorse vengono usate e quando vengono chiuse.
3. Pratica: scrivere codice sicuro con try-with-resources
Esempio: lettura di un file
public static void printFirstLine(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line = reader.readLine();
System.out.println("Prima riga: " + line);
} catch (IOException e) {
System.out.println("Errore: " + e.getMessage());
}
}
Esempio: scrittura su file
public static void writeToFile(String filename, String text) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(text);
} catch (IOException e) {
System.out.println("Errore durante la scrittura: " + e.getMessage());
}
}
Esempio: una risorsa personalizzata
Se scrivi una tua classe che deve essere chiusa, implementa semplicemente AutoCloseable:
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("Risorsa chiusa!");
}
}
Ora si può usare in try-with-resources:
try (MyResource res = new MyResource()) {
// lavoro con la risorsa
}
4. Errori tipici e best practice
Errore n. 1: dimenticare di chiudere la risorsa (senza try-with-resources).
Se non si usa try-with-resources, è facile dimenticare di chiudere un file o uno stream — questo porta a perdite di risorse.
Errore n. 2: tentare di usare try-with-resources con un oggetto che non implementa AutoCloseable.
Se la tua classe non implementa questa interfaccia, il compilatore non permetterà di usarla in try-with-resources.
Errore n. 3: non documentare le eccezioni nell'API.
Se il tuo metodo può lanciare un'eccezione, indica obbligatoriamente questo nella firma (throws) e in Javadoc (@throws). Aiuterà gli altri a usare correttamente il tuo codice.
Errore n. 4: catturare Exception invece di errori specifici.
È meglio catturare solo le eccezioni che ti aspetti realmente e che sai gestire.
GO TO FULL VERSION