CodeGym /Corsi /JAVA 25 SELF /Creare eccezioni personalizzate

Creare eccezioni personalizzate

JAVA 25 SELF
Livello 24, Lezione 1
Disponibile

1. Introduzione

Nella libreria standard di Java esistono già molte eccezioni: NullPointerException, IllegalArgumentException, IOException e altre. Tuttavia, a volte le eccezioni standard non bastano per descrivere in modo chiaro e preciso l'errore verificatosi nel tuo programma.

Esempio pratico:
Stai scrivendo un'app bancaria. L'utente tenta di prelevare più denaro di quanto sia presente sul conto. Si può lanciare IllegalArgumentException. Ma sarà molto più chiaro se proponi una tua eccezione, ad esempio InsufficientFundsException. In questo modo, dal codice si capisce subito cosa è successo.

Le eccezioni personalizzate sono una corretta personalizzazione dell'applicazione. Consentono di gestire i problemi in modo fine e, dal loro nome (se scelto con logica!), è immediatamente chiaro cosa è accaduto. Inoltre, sono auto‑documentanti: i metodi con throws MyException nella firma indicano subito quali errori possono verificarsi. E inoltre — si possono sempre aggiungere campi aggiuntivi (ad esempio, saldo, importo dell'operazione, ecc.).

2. Come creare una propria eccezione?

È molto semplice: crea una nuova classe che estende una delle classi standard di eccezioni.

  • Per le eccezioni verificate (checked) — estendi Exception.
  • Per le non verificate (unchecked) — estendi RuntimeException.

Esempio: eccezione checked

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // Passiamo il messaggio alla superclasse
    }
}

Ora puoi lanciare questa eccezione nel tuo codice:

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("Nome utente o password non validi");
}

Esempio: eccezione unchecked

public class NegativeBalanceException extends RuntimeException {
    public NegativeBalanceException(String message) {
        super(message);
    }
}

Quando usare le checked e quando le unchecked?

  • Checked — se l'errore è previsto e può essere gestito (ad esempio, errore di validazione, assenza di un file, dati utente non corretti).
  • Unchecked — se l'errore è dovuto a un bug nella logica del programma (ad esempio, divisione per zero, violazione di un invariante).

3. Costruttori: come rendere un'eccezione informativa

Di solito nella propria classe di eccezione si implementa almeno un costruttore con il parametro String message. Spesso però se ne aggiungono altri:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException() {
        super();
    }
    public ScoreLimitExceededException(String message) {
        super(message);
    }
    public ScoreLimitExceededException(String message, Throwable cause) {
        super(message, cause);
    }
    public ScoreLimitExceededException(Throwable cause) {
        super(cause);
    }
}

Spiegazione:

  • message — descrizione testuale dell'errore.
  • cause — la causa (un'altra eccezione), se vuoi «incapsulare» un errore in un altro.

Consiglio: se non sai quali costruttori servono — aggiungi almeno quello che accetta una stringa.

4. Uso delle eccezioni personalizzate nel codice

Consideriamo un esempio: abbiamo un utente, l'utente ha dei punti e non si possono aggiungere più di 100.

public class User {
    private String name;
    private int score;

    public User(String name) {
        this.name = name;
        this.score = 0;
    }

    public void addScore(int points) throws ScoreLimitExceededException {
        if (score + points > 100) {
            throw new ScoreLimitExceededException("Limite di punteggio superato! Tentativo di aggiungere: " + points);
        }
        this.score += points;
    }
}

La classe dell'eccezione:

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

Gestione:

try {
    user.addScore(60);
    user.addScore(50); // Qui verrà lanciata l'eccezione!
} catch (ScoreLimitExceededException e) {
    System.out.println("Errore: " + e.getMessage());
}

Risultato:

Errore: Limite di punteggio superato! Tentativo di aggiungere: 50

Potresti chiederti: e se invece di un'eccezione usare semplicemente una condizione if e, ad esempio, restituire false o un altro valore speciale per indicare che l'operazione non è riuscita? Per esempio:

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // Oppure lanciare qualche RuntimeException, se non si vuole gestirla
    }
    this.score += points;
    return true;
}

Sebbene questo approccio possa sembrare più semplice, presenta degli svantaggi quando si tratta di errori seri o di violazioni della logica dell'applicazione.

Primo: restituire false o un altro valore per indicare l'errore obbliga il codice chiamante a verificare sempre il valore di ritorno. Se lo sviluppatore se ne dimentica, l'errore può passare inosservato, con conseguente comportamento imprevedibile del programma. Le eccezioni, invece, impongono la gestione (per le eccezioni verificate) o, quantomeno, segnalano chiaramente il problema se non vengono intercettate.

Secondo: le eccezioni trasmettono la semantica dell'errore in modo più esplicito. Restituire false può significare di tutto: «non riuscito», «non applicabile», «non disponibile». L'eccezione ScoreLimitExceededException dice in modo chiaro e univoco: «limite di punteggio superato». Questo migliora la leggibilità e la manutenibilità del codice.

Terzo: le eccezioni consentono di centralizzare la gestione degli errori. Invece di disseminare controlli if in tutto il codice che chiama addScore, puoi intercettare l'eccezione in un unico punto e prendere la decisione appropriata: mostrare un messaggio all'utente, scrivere nei log o eseguire un rollback della transazione.

Infine, in casi come il superamento di un limite, si tratta davvero di una situazione eccezionale (da qui il nome). Il normale flusso di esecuzione del programma prevede che i punti vengano aggiunti con successo. Se ciò non accade — è una violazione della logica di business o degli invarianti dell'oggetto, che è lo scenario ideale per l'uso delle eccezioni.

5. Aggiungere campi personalizzati alle eccezioni

A volte è utile aggiungere alla propria eccezione dati aggiuntivi che aiutino a gestire l'errore.

Esempio:

public class ScoreLimitExceededException extends Exception {
    private int currentScore;
    private int attemptedAdd;

    public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
        super(message);
        this.currentScore = currentScore;
        this.attemptedAdd = attemptedAdd;
    }

    public int getCurrentScore() { 
        return currentScore; 
    }
    public int getAttemptedAdd() { 
        return attemptedAdd; 
    }
}

Utilizzo:

if (score + points > 100) {
    throw new ScoreLimitExceededException(
        "Limite di punteggio superato!",
        this.score,
        points
    );
}

6. Dettagli utili

Come chiamare le proprie eccezioni?

In Java è prassi chiamare le eccezioni personalizzate con il suffisso Exception: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.

Non chiamare le tue eccezioni semplicemente Error o Warning — potrebbe trarre in inganno altri sviluppatori (e persino te stesso tra qualche settimana).

Dove e quando lanciare le proprie eccezioni?

  • Durante la validazione dei dati utente (ad esempio, nome vuoto, età negativa).
  • In caso di violazione delle regole di business (ad esempio, superamento del limite, tentativo di prelevare più denaro di quanto disponibile sul conto).
  • In caso di errori nell'interazione con servizi esterni (ad esempio, servizio non disponibile, tempo di attesa superato).

7. Errori tipici nella creazione di eccezioni personalizzate

Errore n. 1: ereditarietà dal tipo sbagliato.
Estendi Exception (o RuntimeException), non Throwable o Error.

Errore n. 2: manca un costruttore con il messaggio.
Senza un costruttore che accetta una stringa (String message), le tue eccezioni saranno mute e difficili da debuggare.

Errore n. 3: usare eccezioni standard per la logica di business.
Non lanciare NullPointerException o IllegalArgumentException laddove serve una tua eccezione «parlante».

Errore n. 4: abuso di eccezioni personalizzate.
Non creare una classe di eccezione separata per ogni minima cosa. Se l'errore non è unico per il tuo dominio applicativo, usa le eccezioni standard.

Errore n. 5: assenza di serializzazione (raro, ma succede).
Se la tua eccezione verrà inviata in rete o salvata, conviene implementare implements Serializable. Tuttavia, per le applicazioni semplici non è critico.

1
Compito
JAVA 25 SELF, livello 24, lezione 1
Bloccato
Guardiano del cancello del gioco: Controllo del punteggio del giocatore
Guardiano del cancello del gioco: Controllo del punteggio del giocatore
1
Compito
JAVA 25 SELF, livello 24, lezione 1
Bloccato
Fortezza della password: Protezione dei dati dell'utente
Fortezza della password: Protezione dei dati dell'utente
1
Compito
JAVA 25 SELF, livello 24, lezione 1
Bloccato
Controllo dell'età nel parco divertimenti: Messaggi di errore flessibili
Controllo dell'età nel parco divertimenti: Messaggi di errore flessibili
1
Compito
JAVA 25 SELF, livello 24, lezione 1
Bloccato
Gestione del magazzino di artefatti magici: dettagli precisi sul sovraccarico
Gestione del magazzino di artefatti magici: dettagli precisi sul sovraccarico
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION