Salta al contenuto principale

08 - Gestione degli Errori ed Eccezioni

Gestione eccezioni in Java: try-catch-finally, checked vs unchecked, throw e throws, eccezioni personalizzate e try-with-resources.

Cosa ne pensi di questo articolo?

08 - Gestione degli Errori ed Eccezioni
JavaFoto:Daniil KomovPexels

In Breve

Gestione eccezioni in Java: try-catch-finally, checked vs unchecked, throw e throws, eccezioni personalizzate e try-with-resources.

Gestione degli Errori in Java

La gestione delle eccezioni è fondamentale per scrivere codice robusto. Java fornisce un meccanismo strutturato per intercettare e gestire errori runtime senza bloccare l'applicazione.

Cosa Imparerai

  • Gerarchia delle eccezioni in Java
  • try-catch-finally
  • Eccezioni checked vs unchecked
  • throw e throws
  • Creare eccezioni personalizzate
  • try-with-resources
  • Best practices per la gestione errori

Gerarchia delle Eccezioni

Struttura delle Eccezioni
                    Throwable
                    /        \
               Error          Exception
                |                 |
         VirtualMachineError   IOException (checked)
         OutOfMemoryError      SQLException (checked)
         StackOverflowError    RuntimeException (unchecked)
                                      |
                              NullPointerException
                              IllegalArgumentException
                              IndexOutOfBoundsException

Tipi di Eccezioni

Tipo Descrizione Gestione
Error Problemi gravi della JVM Non gestire
Checked Exception Eccezioni prevedibili Obbligatorio
Unchecked (Runtime) Errori di programmazione Facoltativo

Try-Catch-Finally

Struttura Base
public class GestioneVoti {
    public static void main(String[] args) {
        try {
            // Codice che può generare eccezioni
            int voto = Integer.parseInt("trenta");
            System.out.println("Voto: " + voto);
        } catch (NumberFormatException e) {
            // Gestione dell'eccezione
            System.out.println("Errore: il voto deve essere numerico");
            System.out.println("Dettaglio: " + e.getMessage());
        } finally {
            // Sempre eseguito (anche se c'è eccezione)
            System.out.println("Fine elaborazione voti");
        }
    }
}

Catch Multipli

Gestione Eccezioni Multiple
public class RegistroStudenti {

    private String[] studenti = new String[100];
    private int count = 0;

    public void registraStudente(String nome, String matricolaStr) {
        try {
            // Può lanciare NumberFormatException
            int matricola = Integer.parseInt(matricolaStr);

            // Può lanciare ArrayIndexOutOfBoundsException
            studenti[count] = nome + " (" + matricola + ")";
            count++;

            System.out.println("Studente registrato: " + nome);

        } catch (NumberFormatException e) {
            System.out.println("Errore: matricola non valida");

        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Errore: registro pieno");

        } catch (Exception e) {
            // Catch generico (deve essere l'ultimo)
            System.out.println("Errore imprevisto: " + e.getMessage());
        }
    }

    // Multi-catch (Java 7+)
    public void registraConMultiCatch(String nome, String matricolaStr) {
        try {
            int matricola = Integer.parseInt(matricolaStr);
            studenti[count++] = nome + " (" + matricola + ")";

        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
            // Gestione comune per entrambe le eccezioni
            System.out.println("Errore nella registrazione: " + e.getMessage());
        }
    }
}

Throw e Throws

throw lancia un'eccezione, throws dichiara che un metodo può lanciare eccezioni.

Lanciare Eccezioni
public class ValidatoreVoti {

    // throws dichiara che il metodo può lanciare IllegalArgumentException
    public void registraVoto(String studente, int voto) throws IllegalArgumentException {
        // Validazione con throw
        if (studente == null || studente.trim().isEmpty()) {
            throw new IllegalArgumentException("Nome studente obbligatorio");
        }

        if (voto < 18 || voto > 30) {
            throw new IllegalArgumentException(
                "Voto non valido: " + voto + ". Deve essere tra 18 e 30"
            );
        }

        System.out.println(studente + " ha preso " + voto);
    }

    public static void main(String[] args) {
        ValidatoreVoti validatore = new ValidatoreVoti();

        try {
            validatore.registraVoto("Mario Rossi", 28);
            validatore.registraVoto("", 25);  // Lancerà eccezione
        } catch (IllegalArgumentException e) {
            System.out.println("Errore validazione: " + e.getMessage());
        }
    }
}

Eccezioni Checked vs Unchecked

Checked Exception
import java.io.*;

public class LettoreFile {

    // Checked exception: DEVE essere dichiarata o gestita
    public String leggiFile(String percorso) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(percorso));
        StringBuilder contenuto = new StringBuilder();
        String linea;

        while ((linea = reader.readLine()) != null) {
            contenuto.append(linea).append("\n");
        }

        reader.close();
        return contenuto.toString();
    }

    public static void main(String[] args) {
        LettoreFile lettore = new LettoreFile();

        // Opzione 1: Gestire con try-catch
        try {
            String contenuto = lettore.leggiFile("voti.txt");
            System.out.println(contenuto);
        } catch (IOException e) {
            System.out.println("Impossibile leggere il file: " + e.getMessage());
        }
    }
}
Unchecked Exception
public class CalcolatriceMedia {

    // Unchecked: non obbligatorio dichiarare
    public double calcolaMedia(int[] voti) {
        // Può lanciare ArithmeticException (division by zero)
        // Può lanciare NullPointerException
        if (voti == null) {
            throw new NullPointerException("Array voti non può essere null");
        }

        if (voti.length == 0) {
            throw new IllegalArgumentException("Array voti vuoto");
        }

        int somma = 0;
        for (int voto : voti) {
            somma += voto;
        }

        return (double) somma / voti.length;
    }

    public static void main(String[] args) {
        CalcolatriceMedia calc = new CalcolatriceMedia();

        // Gestione opzionale (ma consigliata)
        try {
            double media = calc.calcolaMedia(new int[]{28, 30, 25});
            System.out.println("Media: " + media);

            // Questo genererà eccezione
            calc.calcolaMedia(null);
        } catch (NullPointerException e) {
            System.out.println("Errore: " + e.getMessage());
        }
    }
}

Quando Usare Checked vs Unchecked

Tipo Quando Usare Esempi
Checked Errori recuperabili, risorse esterne IOException, SQLException
Unchecked Bug, errori di programmazione NullPointer, IllegalArgument

Eccezioni Personalizzate

Eccezione Checked Personalizzata
// Eccezione checked (estende Exception)
public class StudenteNonTrovatoException extends Exception {

    private String matricola;

    public StudenteNonTrovatoException(String matricola) {
        super("Studente con matricola " + matricola + " non trovato");
        this.matricola = matricola;
    }

    public StudenteNonTrovatoException(String matricola, Throwable cause) {
        super("Studente con matricola " + matricola + " non trovato", cause);
        this.matricola = matricola;
    }

    public String getMatricola() {
        return matricola;
    }
}
Eccezione Unchecked Personalizzata
// Eccezione unchecked (estende RuntimeException)
public class VotoNonValidoException extends RuntimeException {

    private int voto;
    private int minimo;
    private int massimo;

    public VotoNonValidoException(int voto, int minimo, int massimo) {
        super(String.format(
            "Voto %d non valido. Deve essere compreso tra %d e %d",
            voto, minimo, massimo
        ));
        this.voto = voto;
        this.minimo = minimo;
        this.massimo = massimo;
    }

    public int getVoto() { return voto; }
    public int getMinimo() { return minimo; }
    public int getMassimo() { return massimo; }
}
Utilizzo Eccezioni Personalizzate
import java.util.*;

public class RegistroEsami {

    private Map<String, List<Integer>> votiStudenti = new HashMap<>();

    public void iscriviStudente(String matricola, String nome) {
        votiStudenti.put(matricola, new ArrayList<>());
        System.out.println("Iscritto: " + nome + " (" + matricola + ")");
    }

    public void registraVoto(String matricola, int voto)
            throws StudenteNonTrovatoException {

        // Verifica studente
        if (!votiStudenti.containsKey(matricola)) {
            throw new StudenteNonTrovatoException(matricola);
        }

        // Verifica voto (unchecked - errore di programmazione)
        if (voto < 18 || voto > 30) {
            throw new VotoNonValidoException(voto, 18, 30);
        }

        votiStudenti.get(matricola).add(voto);
        System.out.println("Voto " + voto + " registrato per " + matricola);
    }

    public double getMedia(String matricola) throws StudenteNonTrovatoException {
        if (!votiStudenti.containsKey(matricola)) {
            throw new StudenteNonTrovatoException(matricola);
        }

        List<Integer> voti = votiStudenti.get(matricola);
        if (voti.isEmpty()) {
            return 0;
        }

        return voti.stream()
                   .mapToInt(Integer::intValue)
                   .average()
                   .orElse(0);
    }

    public static void main(String[] args) {
        RegistroEsami registro = new RegistroEsami();

        registro.iscriviStudente("MAT001", "Mario Rossi");
        registro.iscriviStudente("MAT002", "Laura Bianchi");

        try {
            registro.registraVoto("MAT001", 28);
            registro.registraVoto("MAT001", 30);
            registro.registraVoto("MAT003", 25);  // Studente non esiste

        } catch (StudenteNonTrovatoException e) {
            System.out.println("Errore: " + e.getMessage());
            System.out.println("Matricola cercata: " + e.getMatricola());
        }

        try {
            registro.registraVoto("MAT002", 35);  // Voto non valido

        } catch (VotoNonValidoException e) {
            System.out.println("Errore voto: " + e.getMessage());
        } catch (StudenteNonTrovatoException e) {
            System.out.println("Studente non trovato");
        }
    }
}

Try-With-Resources

Introdotto in Java 7, gestisce automaticamente la chiusura delle risorse che implementano AutoCloseable.

Prima di Java 7
// Vecchio modo: finally per chiudere
public void leggiFileVecchio(String path) {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(path));
        String linea;
        while ((linea = reader.readLine()) != null) {
            System.out.println(linea);
        }
    } catch (IOException e) {
        System.out.println("Errore lettura: " + e.getMessage());
    } finally {
        // Chiusura manuale (verbosa e error-prone)
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                System.out.println("Errore chiusura: " + e.getMessage());
            }
        }
    }
}
Con Try-With-Resources (Java 7+)
import java.io.*;
import java.nio.file.*;

public class GestoreFile {

    // Try-with-resources: chiusura automatica
    public void leggiFile(String path) {
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            String linea;
            while ((linea = reader.readLine()) != null) {
                System.out.println(linea);
            }
        } catch (IOException e) {
            System.out.println("Errore: " + e.getMessage());
        }
        // reader.close() chiamato automaticamente!
    }

    // Risorse multiple
    public void copiaFile(String sorgente, String destinazione) {
        try (
            BufferedReader reader = new BufferedReader(new FileReader(sorgente));
            BufferedWriter writer = new BufferedWriter(new FileWriter(destinazione))
        ) {
            String linea;
            while ((linea = reader.readLine()) != null) {
                writer.write(linea);
                writer.newLine();
            }
            System.out.println("File copiato con successo");

        } catch (IOException e) {
            System.out.println("Errore copia: " + e.getMessage());
        }
    }

    // Con NIO.2 (più moderno)
    public void leggiFileNio(String path) {
        try {
            String contenuto = Files.readString(Path.of(path));
            System.out.println(contenuto);
        } catch (IOException e) {
            System.out.println("Errore: " + e.getMessage());
        }
    }
}
Risorsa AutoCloseable Personalizzata
public class ConnessioneDatabase implements AutoCloseable {

    private String nome;
    private boolean connesso;

    public ConnessioneDatabase(String nome) {
        this.nome = nome;
        this.connesso = true;
        System.out.println("Connessione aperta: " + nome);
    }

    public void eseguiQuery(String query) {
        if (!connesso) {
            throw new IllegalStateException("Connessione chiusa");
        }
        System.out.println("Eseguo: " + query);
    }

    @Override
    public void close() {
        if (connesso) {
            connesso = false;
            System.out.println("Connessione chiusa: " + nome);
        }
    }

    public static void main(String[] args) {
        // Chiusura automatica garantita
        try (ConnessioneDatabase db = new ConnessioneDatabase("ScuolaDB")) {
            db.eseguiQuery("SELECT * FROM studenti");
            db.eseguiQuery("SELECT * FROM esami");
        }
        // Output:
        // Connessione aperta: ScuolaDB
        // Eseguo: SELECT * FROM studenti
        // Eseguo: SELECT * FROM esami
        // Connessione chiusa: ScuolaDB
    }
}

Esempio Completo: Sistema di Gestione Esami

Eccezioni del Dominio
package scuola.eccezioni;

public class EsameException extends Exception {
    public EsameException(String message) {
        super(message);
    }

    public EsameException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class StudenteNonIscrittoException extends EsameException {
    private String matricola;
    private String corso;

    public StudenteNonIscrittoException(String matricola, String corso) {
        super(String.format(
            "Studente %s non iscritto al corso %s",
            matricola, corso
        ));
        this.matricola = matricola;
        this.corso = corso;
    }

    public String getMatricola() { return matricola; }
    public String getCorso() { return corso; }
}

public class EsameGiaSostenutoException extends EsameException {
    private String matricola;
    private String esame;
    private int votoEsistente;

    public EsameGiaSostenutoException(String matricola, String esame, int voto) {
        super(String.format(
            "Studente %s ha già sostenuto %s con voto %d",
            matricola, esame, voto
        ));
        this.matricola = matricola;
        this.esame = esame;
        this.votoEsistente = voto;
    }
}
Sistema Gestione Esami
package scuola;

import scuola.eccezioni.*;
import java.util.*;

public class GestioneEsami {

    private Map<String, Set<String>> iscrizioni = new HashMap<>();
    private Map<String, Map<String, Integer>> verbali = new HashMap<>();

    public void iscriviStudente(String matricola, String corso) {
        iscrizioni.computeIfAbsent(corso, k -> new HashSet<>())
                  .add(matricola);
        System.out.println(matricola + " iscritto a " + corso);
    }

    public void registraEsame(String matricola, String corso, int voto)
            throws StudenteNonIscrittoException,
                   EsameGiaSostenutoException,
                   VotoNonValidoException {

        // Verifica iscrizione
        Set<String> studentiCorso = iscrizioni.get(corso);
        if (studentiCorso == null || !studentiCorso.contains(matricola)) {
            throw new StudenteNonIscrittoException(matricola, corso);
        }

        // Verifica voto valido
        if (voto < 18 || voto > 30) {
            throw new VotoNonValidoException(voto, 18, 30);
        }

        // Verifica esame già sostenuto
        String chiaveVerbale = matricola + "_" + corso;
        if (verbali.containsKey(matricola)) {
            Map<String, Integer> esamiStudente = verbali.get(matricola);
            if (esamiStudente.containsKey(corso)) {
                throw new EsameGiaSostenutoException(
                    matricola, corso, esamiStudente.get(corso)
                );
            }
        }

        // Registra voto
        verbali.computeIfAbsent(matricola, k -> new HashMap<>())
               .put(corso, voto);
        System.out.println("Esame registrato: " + matricola +
                          " - " + corso + ": " + voto);
    }

    public void stampaCarriera(String matricola) {
        Map<String, Integer> esami = verbali.get(matricola);
        if (esami == null || esami.isEmpty()) {
            System.out.println("Nessun esame sostenuto per " + matricola);
            return;
        }

        System.out.println("\n=== Carriera di " + matricola + " ===");
        double somma = 0;
        for (Map.Entry<String, Integer> entry : esami.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
            somma += entry.getValue();
        }
        System.out.printf("Media: %.2f%n", somma / esami.size());
    }

    public static void main(String[] args) {
        GestioneEsami gestione = new GestioneEsami();

        // Setup
        gestione.iscriviStudente("MAT001", "Programmazione");
        gestione.iscriviStudente("MAT001", "Database");
        gestione.iscriviStudente("MAT002", "Programmazione");

        // Registrazione esami con gestione eccezioni
        try {
            gestione.registraEsame("MAT001", "Programmazione", 28);
            gestione.registraEsame("MAT001", "Database", 30);
            gestione.registraEsame("MAT002", "Programmazione", 25);

            // Errori intenzionali per test
            gestione.registraEsame("MAT001", "Programmazione", 27);

        } catch (StudenteNonIscrittoException e) {
            System.err.println("ERRORE ISCRIZIONE: " + e.getMessage());

        } catch (EsameGiaSostenutoException e) {
            System.err.println("ERRORE DUPLICATO: " + e.getMessage());

        } catch (VotoNonValidoException e) {
            System.err.println("ERRORE VOTO: " + e.getMessage());
        }

        // Stampa carriere
        gestione.stampaCarriera("MAT001");
        gestione.stampaCarriera("MAT002");
    }
}

Best Practices

Regole d'Oro per le Eccezioni

  • Non usare eccezioni per il flusso normale: Le eccezioni sono per casi eccezionali
  • Catch specifici prima di generici: Ordina dal più specifico al più generale
  • Non catturare e ignorare: Almeno logga l'errore
  • Usa try-with-resources: Per risorse che vanno chiuse
  • Documenta le eccezioni: Usa @throws in Javadoc
  • Preferisci unchecked per errori di programmazione: NullPointer, IllegalArgument
  • Crea eccezioni di dominio: Per errori specifici della business logic
  • Includi contesto utile: Messaggi chiari con dati rilevanti
Anti-Pattern da Evitare
// ❌ SBAGLIATO: catch vuoto
try {
    rischiosa();
} catch (Exception e) {
    // Non fare nulla = bug nascosto!
}

// ❌ SBAGLIATO: catch troppo generico
try {
    rischiosa();
} catch (Throwable t) {  // Cattura anche Error!
    // ...
}

// ❌ SBAGLIATO: eccezioni per controllo flusso
try {
    int i = 0;
    while (true) {
        array[i++].process();  // Aspetta IndexOutOfBounds
    }
} catch (IndexOutOfBoundsException e) {
    // Fine array
}

// ✅ CORRETTO
for (int i = 0; i < array.length; i++) {
    array[i].process();
}

Conclusione

La gestione delle eccezioni è fondamentale per codice robusto. Comprendere la gerarchia, usare try-with-resources e creare eccezioni di dominio sono competenze essenziali.

Punti Chiave da Ricordare

  • Checked: Errori recuperabili, obbligatorio gestirli
  • Unchecked: Bug di programmazione, gestione facoltativa
  • try-with-resources: Per chiusura automatica risorse
  • Eccezioni personalizzate: Per errori di dominio specifici
  • finally: Sempre eseguito, utile per cleanup
  • throw: Lancia eccezione, throws: Dichiara nel metodo

Nel prossimo articolo esploreremo Input/Output in Java: file, stream, NIO.2 e gestione dei dati.

Domande frequenti

Qual è l'importanza della gestione delle eccezioni nella programmazione in Java?

La gestione delle eccezioni è fondamentale per scrivere codice robusto, poiché consente di intercettare e gestire errori runtime senza bloccare l'applicazione.

Qual è la differenza tra le eccezioni checked e unchecked in Java?

Le eccezioni checked sono quelle che devono essere esplicitamente catturate o lanciate, mentre le eccezioni unchecked sono quelle che possono essere ignorate ma non sono necessariamente gestite.

Qual è il vantaggio di utilizzare la clausola "try-with-resources" in Java?

La clausola "try-with-resources" consente di gestire automaticamente la chiusura delle risorse che implementano l'interfaccia Closeable, riducendo così la possibilità di errori.

Qual è il modo corretto per dichiarare che un metodo può lanciare eccezioni in Java?

Il modo corretto per dichiarare che un metodo può lanciare eccezioni in Java è utilizzare le parole chiave "throws" o "throw", a seconda del caso.

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti