1. Introduzione al blocco finally
Quando lavori con risorse — file, connessioni di rete, database — è importante essere sicuri che vengano chiuse o rilasciate sempre, anche se durante l'esecuzione si verifica un errore. In Java esiste un blocco speciale — finally.
Come funziona finally?
Il blocco finally è parte della costruzione try-catch-finally. Il codice dentro finally viene eseguito sempre (se presente) — indipendentemente dal fatto che si sia verificata un'eccezione oppure no. Anche se in try c'è un return o viene lanciata un'eccezione — finally verrà comunque eseguito (a meno che non si spenga il computer o non si termini forzatamente il programma tramite System.exit(0)).
Sintassi:
try {
// Codice in cui può verificarsi un'eccezione
} catch (ExceptionType e) {
// Gestione dell'errore
} finally {
// Questo codice verrà eseguito sempre!
}
Esempio
try {
System.out.println("Inizio del lavoro");
int result = 10 / 0; // qui si verificherà un errore
System.out.println("Risultato: " + result);
} catch (ArithmeticException e) {
System.out.println("Errore: divisione per zero");
} finally {
System.out.println("Questo codice verrà eseguito in ogni caso");
}
Output:
Inizio del lavoro
Errore: divisione per zero
Questo codice verrà eseguito in ogni caso
Cosa succede?
- Nel try proviamo a dividere due numeri e otteniamo un errore.
- Se durante la divisione si verifica un errore — catch lo gestirà.
- Ma! In ogni caso finally verrà eseguito e scriverà un messaggio in console.
2. finally senza catch
Esistono 3 varianti della costruzione:
- Completa: try-catch-finally
- Senza finally: try-catch
- Senza catch: try-finally
La terza variante si usa quando a intercettare e gestire l'errore sarà un metodo a un livello superiore. Ma il blocco finally serve per garantire l'esecuzione di un determinato codice:
- Chiusura di file, connessioni di rete, database.
- Rilascio di qualunque risorsa (ad esempio, lock).
- Logging: registrazione di informazioni sulla conclusione dell'operazione.
Esempio:
try {
System.out.println("Dividiamo i numeri");
int result = 10 / 0; // errore!
System.out.println("Risultato: " + result);
} finally {
System.out.println("Blocco finally eseguito");
}
Risultato:
Dividiamo i numeri
Blocco finally eseguito
Exception in thread "main" java.lang.ArithmeticException: / by zero
Quando finally NON viene eseguito?
Viene eseguito quasi sempre. Fanno eccezione i casi in cui:
- Il programma è stato terminato forzatamente con: System.exit(0).
- Il thread in cui si esegue finally è stato «ucciso» forzatamente.
- Il computer è stato spento.
3. L'operatore throw: come generare un'eccezione manualmente
A volte Java «lancia» eccezioni da sola (ad esempio, divisione per zero, accesso oltre i limiti dell'array). Ma ci sono situazioni in cui tu vuoi dichiarare esplicitamente: «È un errore! Non posso continuare l'esecuzione!». Per questo in Java c'è l'operatore throw.
Analogia: Se sei in un negozio e vedi merce scaduta — «lanci» un reclamo. Così nel codice: se qualcosa non va — lanci un'eccezione.
Sintassi di throw
throw new ExceptionType("Messaggio di errore");
ExceptionType — qualsiasi classe che estende Throwable (di solito Exception o RuntimeException). Tra le parentesi c'è un messaggio che aiuta a capire cosa non ha funzionato.
Esempio: validazione degli argomenti del metodo
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Il divisore non può essere zero");
}
return a / b;
}
Utilizzo:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("Risultato: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Errore: " + e.getMessage());
}
}
Risultato:
Errore: Il divisore non può essere zero
Quando usare throw?
- Validazione degli argomenti del metodo (ad esempio, se è arrivato null o dati non validi).
- Verifica dello stato dell'oggetto (ad esempio, tentare di prelevare denaro da un conto con 0 euro).
- All'interno di catch — se vuoi «rilanciare» l'eccezione più in alto (ad esempio, per aggiungere informazioni).
4. Combinare try-catch-finally e throw
Talvolta queste costruzioni lavorano insieme. Per esempio, intercetti un errore, ma dopo decidi di lanciarne uno tuo, più informativo.
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("Il divisore non può essere zero");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("La stringa '" + text + "' non è un numero");
} finally {
System.out.println("Tentativo di elaborare la stringa: " + text);
}
}
Utilizzo:
try {
int result = parseAndDivide("42a", 2);
System.out.println("Risultato: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Errore: " + e.getMessage());
}
Risultato:
Tentativo di elaborare la stringa: 42a
Errore: La stringa '42a' non è un numero
Dettaglio importante: return e finally
Anche se nel blocco try c'è un return, finally verrà comunque eseguito!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("finally verrà eseguito comunque!");
}
}
La chiamata a getValue() stamperà:
finally verrà eseguito comunque!
5. Errori tipici nell'uso di finally e throw
Errore n. 1: hai dimenticato di chiudere una risorsa senza finally.
Problema molto comune: hai aperto un file, non l'hai chiuso — perdita di risorse. Usa sempre finally (oppure try-with-resources, di cui parleremo più avanti).
Errore n. 2: hai lanciato un'eccezione ma non l'hai gestita.
Se lanci un'eccezione con throw, ma non la intercetti da nessuna parte (non c'è try-catch), il programma terminerà in modo anomalo. Pensa sempre a chi intercetterà la tua eccezione.
Errore n. 3: return dentro finally.
Se per errore hai scritto return dentro finally, esso «sovrascriverà» tutti i precedenti return o throw. Questo può portare a bug molto difficili da individuare. È fortemente sconsigliato!
public int tricky() {
try {
return 1;
} finally {
return 2; // PERICOLOSO: verrà restituito 2, non 1!
}
}
Risultato: verrà restituito 2, sebbene in try ci fosse 1.
Errore n. 4: perdita di informazioni sull'eccezione.
Se intercetti un'eccezione e poi ne lanci una nuova, senza conservare le informazioni sulla precedente (e), perdi lo stack di chiamate, il che rende più difficile il debug. Meglio scrivere così:
catch (NumberFormatException e) {
throw new IllegalArgumentException("Errore di conversione", e);
}
GO TO FULL VERSION