Nuove eccezioni

Abbiamo esaminato finora la gerarchia delle eccezioni in Java e le modalità di utilizzo delle eccezioni già definite e rese disponibili dal linguaggio stesso.
Ma se volessimo definire ed utilizzare delle nostre eccezioni personalizzate? Magari specifiche per determinate problematiche, che potrebbero emergere nell'applicazione che stiamo realizzando?

In tali situazioni, il programmatore può definire nuove eccezioni.

Per fare ciò è necessario creare una nuova classe che derivi (o specializzi) dalla classe Exception.
Vediamo nel dettaglio cosa vuol dire creare una nuova eccezione in linguaggio Java.
Supponiamo di voler definire una nostra classe per trattare le anomalie di divisione per zero.
Dobbiamo definire una classe, che possiamo chiamare DivisoreNulloException, che deve specializzare la classe Exception.
Per indicare che una classe ne specializza un'altra, in Java occorre utilizzare, nella definizione della classe, la parola chiave extends, seguita dalla classe che viene specializzata.

Nota:
Osservare anche l'utilizzo del suffisso Exception (per convenzione, comprensione e leggibilità del codice) nell'attribuzione del nome alla nuova classe eccezione.

L'esempio che segue mostra il codice da scrivere in Java.

// notare l'uso della parola chiave extends 
// per estendere la classe Exception
public class DivisoreNulloException extends Exception { 
    
}

Fatto ciò, possiamo ora utilizzare la nuova tipologia di eccezione creata, in una nostra funzione di divisione tra due numeri interi.
Ma cosa si intende precisamente con il termine utilizzare?

Utilizzare una tipologia di eccezione vuol dire sollevare una istanza specifica della classe eccezione, quando si verificano determinate condizioni.

Per sollevare una istanza di eccezione, nel linguaggio Java, occorre utilizzare la parola chiave throw.
Inoltre, ogni volta che una funzione solleva una eccezione, essa è obbligata, in Java, a dichiararlo nella sua definizione, tramite la parola chiave throws.
Ritorneremo a parlare dell'utilizzo della parola chiave throws quando approfondiremo il principio handle or declare.
In codice Java, quanto detto viene implementato come mostrato di seguito:

// notare l'uso di throws nella definizione della funzione
// per comunicare che la funzione potrebbe, 
// al verificarsi di determinate condizioni,
// sollevare l'eccezione DivisoreNulloException                            
public static int quoziente(int dividendo, int divisore) throws DivisoreNulloException {
    if (divisore == 0) { // se si verifica la condizione
        // crea l'istanza dell'eccezione e la solleva utilizzando throw
        throw new DivisoreNulloException(); 
    }
    return dividendo / divisore;
}

Nota:
L'esecuzione dell'istruzione throw interrompe il flusso delle istruzioni della funzione in cui è invocata e restituisce il controllo alla funzione chiamante, che deve gestirla attraverso l'uso del blocco try-catch.

Mettiamo ora insieme quanto esaminato fino a questo punto e, supponendo di definire la nuova tipologia di eccezione (DivisoreNulloException) esattamente come visto in precedenza, quindi semplicemente derivando dalla classe Exception, senza altro codice, riscriviamo il programma per il calcolo del quoziente della divisione tra due numeri interi richiesti all'utente.
Il codice in Java potrebbe essere quello che segue:

import java.util.Scanner;

public class CalcoloQuoziente {
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        System.out.print("Inserisci il dividendo: ");
        int dividendo = sc.nextInt();
        
        System.out.print("Inserisci il divisore: ");
        int divisore = sc.nextInt();
        
        String msg = "";
        try {
            int quoziente = quoziente(dividendo, divisore);
        
            // se non viene sollevata nessuna eccezione 
            // costruisco il messaggio della divisione
            msg = "Il quoziente della divisione tra " + dividendo + 
                " e " + divisore + " è " + quoziente;
        } catch(DivisoreNulloException ex) {
            // se viene sollevata l'eccezione di DivisoreNulloException
            // costruisco il messaggio di errore
            msg = "Errore nella divisione: "+ex.getMessage();
        } finally {
            // stampo il messaggio costruito in precedenza
            System.out.println(msg);
        }
    }
    
    public static int quoziente(int dividendo, int divisore) throws DivisoreNulloException {
        if (divisore == 0) {
            throw new DivisoreNulloException();
        }
        return dividendo / divisore;
    }
}

L'output dell'esecuzione del programma, nel caso in cui l'utente inserisca i valori 10, come dividendo, e 0 come divisore, è il seguente:

Inserisci il dividendo: 10
Inserisci il divisore: 0
Errore nella divisione: null

Notiamo che, in assenza di codice nella classe DivisoreNulloException, il programma segnala correttamente l'anomalia, ma il suo messaggio descrittivo non è definito, quindi l'esecuzione visualizza il valore null, quando viene chiesto di stampare l'esito del metodo getMessage.

Se modifichiamo il codice della classe DivisoreNulloException, definendo il comportamento del metodo getMessage, come mostrato nel codice seguente, senza cambiare nulla nella classe CalcolaQuoziente, l'output dell'esecuzione diventa quello mostrato di seguito.


public class DivisoreNulloException extends Exception { 
    // definiamo il comportamento del 
    // metodo getMessage
    @Override
    public String getMessage() {
        return "Divisore nullo!";
    }
}

Inserisci il dividendo: 10
Inserisci il divisore: 0
Errore nella divisione: Divisore nullo!
Nota:
Il metodo getMessage è definito nella classe Exception, ma, come abbiamo appena visto, restituisce null, per cui per modificarne il comportamento occorre ridefinirlo, ossia fare un override. Per questo motivo, come abbiamo già visto per il metodo toString, quando viene ridefinito il metodo getMessage occorre indicare l'etichetta @Override.

Facciamo ancora un passo oltre.

Se volessimo che il messaggio restituito dalla classe DivisoreNulloException, attraverso il metodo getMessage sia:

Impossibile eseguire la divisione tra 10 e 0, il divisore è nullo!

dovremmo, in qualche modo, comunicare alla classe eccezione quali sono i valori per i quali si sta cercando di calcolare il quoziente.

Potremmo, ad esempio, utilizzare il costruttore della classe eccezione per passare tali informazioni.
Il codice della classe DivisoreNulloException diventera allora il seguente:


public class DivisoreNulloException extends Exception { 
    private int dividendo;

    public DivisoreNulloException(int dividendo) {
        this.dividendo = dividendo;
    }

    // definiamo il comportamento del 
    // metodo getMessage
    @Override
    public String getMessage() {
        return "Impossibile eseguire la divisione tra " +
            dividendo + " e 0", il divisore è nullo!";
    }
}

Questa volta, però, dobbiamo fare una piccola modifica al codice della classe CalcolaQuoziente perché, nel momento in cui la funzione, incaricata di eseguire la divisione tra dividendo e divisore, rileva la condizione di divisore nullo, deve comunicare, attraverso il costruttore della classe DivisoreNulloException il valore del dividendo.
Il codice della classe CalcolaQuoziente verrebbe modificato come segue:

import java.util.Scanner;

public class CalcoloQuoziente {
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        System.out.print("Inserisci il dividendo: ");
        int dividendo = sc.nextInt();
        
        System.out.print("Inserisci il divisore: ");
        int divisore = sc.nextInt();
        
        String msg = "";
        try {
            int quoziente = quoziente(dividendo, divisore);
        
            // se non viene sollevata nessuna eccezione 
            // costruisco il messaggio della divisione
            msg = "Il quoziente della divisione tra " + dividendo + 
                " e " + divisore + " è " + quoziente;
        } catch(DivisoreNulloException ex) {
            // se viene sollevata l'eccezione di DivisoreNulloException
            // costruisco il messaggio di errore
            msg = "Errore nella divisione: "+ex.getMessage();
        } finally {
            // stampo il messaggio costruito in precedenza
            System.out.println(msg);
        }
    }
    
    public static int quoziente(int dividendo, int divisore) throws DivisoreNulloException {
        if (divisore == 0) {
            // notare il passaggio dell'informazione
            // del dividendo alla classe 
            // DivisoreNulloException
            // attraverso il suo costruttore
            throw new DivisoreNulloException(dividendo); 
        }
        return dividendo / divisore;
    }
}

Se mandiamo in esecuzione l'applicazione così trasformata, l'output, in caso di valori di dividendo e divisore pari a 10 e 0, è il seguente:

Inserisci il dividendo: 10
Inserisci il divisore: 0
Impossibile eseguire la divisione tra 10 e 0, il divisore è nullo!