Menu


Trascrizione diapositive

1. Eccezioni ed asserzioni

  • Complessità: ALTA.


2. Cos’è un’eccezione

  • Un eccezione non è altro che una situazione imprevista in cui il flusso di un’applicazione Java può incappare.
  • È possibile gestire un eccezione grazie a cinque parole chiave del linguaggio: «try», «catch», «finally», «throw» e «throws».
  • Sarà anche possibile generare delle eccezioni personalizzate, in modo tale da decidere come e soprattutto dove gestirle.
  • Simile ma assai differente è invece il concetto di errore, in quanto esso è definito come una situazione imprevista che non dipende da una svista dello sviluppatore o dal cliente, ma dipende da errori causati dalla non corretta esecuzione del programma, come ad esempio l’esaurimento delle risorse di memoria della virtual machine; chiaramente un errore non è gestibile.


3. Panoramica sulle classi

  • Java mette a disposizione una serie di classi per la corretta gestione delle eccezioni e degli errori.La classe principale è detta «Throwable», essa viene estesa da:
    - La classe «Error» per generare gli errori.
    - La classe «Exception» per il management delle eccezioni.
  • A sua volta la classe «Error» e la classe «Exception» vengono estese da altre peculiari sottoclassi, come mostrato nella seguente immagine:

[Immagine] Panoramica sulle classi



4. Gestione delle eccezioni

  • Mentre sviluppiamo un programma, se pensiamo che una parte del suo codice possa provocare un’eccezione, codesta parte va prontamente circoscritta in un blocco «try»; per avvalorare quest’ultima affermazione basta prendere in esame un frammento del tipo:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    int c = a/b;
    System.out.println(c);
}

}

  • In questo caso verrà generata un eccezione di tipo «ArithmeticException», perciò la JVM visualizzerà in output il seguente messaggio di errore:

Exception in thread "main" java.lang.ArithmeticException: / by zero

at programma.Programma.main (Programma.java:5)

  • Esaminando l’output mostrato dalla JVM, si evince che l’errore è stato provocato perché alla riga 5 si è tentato di dividere per 0; è evidente che si tratta di un messaggio molto chiaro per noi sviluppatori; ma è altrettanto facilmente ipotizzabile che l’utente finale potrebbe avere più di una difficoltà nel decifrare un simile testo.
  • Come abbiamo già in precedenza affermato in casi come questo è fondamentale fare uso del blocco «try», in modo tale da gestire l’eccezione in modo personalizzato.


5. Blocco try-catch

  • Procediamo quindi alla stesura del codice:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    try {

    int c = a/b;
    System.out.println(c);

    }
    catch (ArithmeticException exc) {

    System.out.println("Errore nella divisione.");

    }

}

}

Notiamo che quando la JVM eseguirà la prima riga del blocco «try», verrà lanciata automaticamente l’eccezione chiamata «ArithmeticException», quindi sarà eseguito il relativo blocco «catch» sottostante.

  • Perciò non sarà eseguita l’istruzione riguardante la divisione, che avrebbe tuttavia causato la visualizzazione del messaggio di errore visto nella precedente slide, bensì verrà mostrata in output la stringa «Errore nella divisione.», sicuramente un messaggio più comprensibile per un eventuale fruitore finale dell’applicativo.


6. Approfondimento sul blocco catch

  • Il blocco «catch» potrebbe risultare piuttosto strano al programmatore inesperto, ma presto ci si fa l’abitudine, visto che è praticamente presente in tutti i programmi scritti in Java.
  • In particolare «catch» deve dichiarare un parametro (come se fosse un metodo) del tipo dell’eccezione che deve essere catturata; Ad esempio se dovessimo lanciare una «ArithmeticException», dovremmo scrivere qualcosa del tipo:

catch (ArithmeticException exc) {
                                               System.out.println("Errore.");
}

Per lanciare questa eccezione servirà nel blocco «try» il seguente codice:

ArithmeticException exc = new ArithmeticException();
throw exc; // Esegue il bloccocatch relativo all’ArithmeticException

Il codice sovraesposto negli esempi fin ora suggeriti non è stato mai scritto e neanche accennato teoricamente, si è scelto di non farlo proprio perché non è necessario, in quanto viene eseguito automaticamente dalla JVM al momento opportuno; è comunque bene sapere che «exc» è un nome (scelto a caso) di un oggetto di tipo «ArithmeticException», quindi, a partire da «exc» si possono eseguire tutti i metodi pubblici invocabili dalla classe «ArithmeticException», come ad esempio «printStackTrace()» che produce in output i messaggi informativi che il programma avrebbe prodotto se l’eccezione non fosse stata gestita.



7. Gestione delle eccezioni

  • Quindi scrivere in questo modo utilizzando «printStackTrace()»:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    try {

    int c = a/b;
    System.out.println(c);

    }

    catch (ArithmeticException exc) {

    exc.printStackTrace();

    }

}

}

È equivalente a scrivere in questo modo:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    int c = a/b;
    System.out.println(c);
}

}

  • Tuttavia nel primo caso abbiamo la possibilità di gestire l’eccezione lanciata (magari aggiungendo ulteriori informazioni), mentre nel secondo caso non sarà possibile gestire l’eccezione.


8. Uso corretto del blocco catch

  • Il blocco «catch» come precedentemente affermato è fondamentale per gestire le eccezioni, ma è anche molto importante usarlo nel modo più corretto possibile, nel senso che è necessario dichiarare sempre un’eccezione del tipo giusto. Ad esempio:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    try {

    int c = a/b;
    System.out.println(c);

    }
    catch (NullPointerException exc) {

    System.out.println("Errore.");

    }

}

}

Produrrebbe un’eccezione non gestita, provocando di conseguenza la visualizzazione del classico messaggio informativo, ignorando il messaggio personalizzato.
Questo perché il blocco «try» non ha mai lanciato un’eccezione di tipo «NullPointerException» ma una «ArithmeticException».



9. Generalizzazione delle eccezioni

  • E’ possibile fare in modo che un blocco «catch» possa gestire qualsiasi tipo di eccezione, per farlo basta scrivere un qualcosa del tipo:

catch (Exception exc) {

System.out.println("Errore.");

}

  • Essendo «Exception» la classe da cui discendono tutte le altre eccezioni, qualsiasi tipo di eccezione venga generata, verrà gestita dal medesimo blocco «catch».


10. Eccezioni in cascata

  • Volendo è anche possibile far seguire ad un blocco «try» più blocchi «catch», come nel seguente esempio:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    try {

    int c = a/b;
    System.out.println(c);

    }
    catch (ArithmeticException exc) { // Eccezione prevista

    System.out.println("Errore di tipo aritmetico.");

    }
    catch (NullPointerException exc) { // Eccezione prevista

    System.out.println("Errore di puntamento.");

    }
    catch (Exception exc) { // Eccezione non prevista

    System.out.println("Errore generico.");

    }

}

}

  • In questo modo il nostro software potrà gestire ogni eccezione si dovesse originare, nel caso di un eccezione non prevista l’ultimo blocco «catch» risolverà il problema.


11. Osservazione sulle eccezioni in cascata

  • È anche molto importante l’ordine con cui vengono scritti i blocchi «catch», ad esempio in questo caso:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    try {

    int c = a/b;
    System.out.println(c);

    }
    catch (Exception exc) { // Eccezione non prevista

    System.out.println("Errore generico.");

    }
    catch (ArithmeticException exc) { // Eccezione prevista

    System.out.println("Errore di tipo aritmetico.");

    }
    catch (NullPointerException exc) { // Eccezione prevista

    System.out.println("Errore di puntamento.");

    }

}

}

Il primo blocco «catch» gestirà qualsiasi tipo di eccezione, rendendo in questo modo superflui gli ultimi due «catch»; in questa circostanza il compilatore javac segnalerà errore.



12. Blocco finally

  • È possibile far seguire ad un blocco «try» o ad un blocco «try-catch», un altro blocco chiamato «finally»; ad esempio:

[Download]

public class Programma {

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    try {

    int c = a/b;
    System.out.println(c);

    }
    catch (ArithmeticException exc) { // Eccezione prevista

    System.out.println("Errore di tipo aritmetico.");

    }
    finally {

    System.out.println("Operazione aritmetica terminata");

    }

}

}

  • Tutto quello che è contenuto in un blocco «finally» viene eseguito in ogni caso.


13. Tipi di comuni di Eccezioni

  • Di seguito vengono riportate le più comuni eccezioni presenti nel package «java.lang» (importato automaticamente in ogni file Java); chiaramente l’elenco sarà ampliato nel proseguo del corso con l’introduzione delle librerie «java.io» e «java.sql».

Eccezione

Descrizione

ArithmeticException

Se un espressione non ha un risultato.

NullPointerException

Se si fa riferimento ad un qualcosa che punta a «null».

EOFException

Se viene effettuato un tentativo di leggere oltre la fine di un file.

FileNotFoundException

Se un tentativo di accesso ad un file esistente sul disco non riesce.

ArrayIndexOutOfBoundsException

Se si prova ad accedere ad un indice di un array troppo alto.

ClassCastException

Se si prova ed effettuare il casting di un oggetto ma l’oggetto di destinazione non è compatibile.



14. Eccezioni personalizzate

  • Un eccezione personalizzata è un nuovo tipo di imprevisto introdotto di proposito, non definito in nessuna sottoclasse di «Exception».
  • Per incappare in un evento personalizzato è necessario generare una nuova classe che erediti da un’altra eccezione già esistente, magari effettuando override di metodi come «toString()».


15. Esempio di eccezione personalizzata

  • Immaginiamo di gestire le iscrizioni ad un club privato cui massimo numero di iscritti è limitato a «500», bisognerà dover lanciare un eccezione non appena si tenti di superare questo limite autoimposto; chiaramente nessun tipo di imprevisto fin qui menzionato potrà gestire efficacemente una situazione del genere.
  • Occorrerà realizzare una nuova eccezione nel seguente modo:

class IscrizioneException extends Exception {

public IscrizioneException() {
    super("Impossibile iscriversi");
}
@Override
public String toString() {
    return getMessage() + ": posti esauriti.";
}

}

  • L’eccezione personalizzata  appena creata non potrà essere lanciata automaticamente, nella prossima slide capiremo come generare l’imprevisto non appena verrà superata la soglia massima di iscritti.


16. Lanciare un eccezione personalizzata

  • Per dare luogo ad un eccezione a partire da una classe, bisogna impiegare in un blocco «try» la sintassi già accennata in precedenza:

IscrizioneException exc = new IscrizioneException();
throw exc; // Esegue il bloccocatch relativo all’ IscrizioneException

  • Nel nostro caso dopo aver creato l’imprevisto, è necessario lanciarlo dall’interno di una struttura di controllo decisionale; segue il codice sorgente della classe relativa al club privato:

class Club {

int numeroIscritti = 0;
void Iscriviti() {
    try {

    if (numeroIscritti == 500) {

    IscrizioneException exc = new IscrizioneException();
    throw exc;

    }
    numeroIscritti++;

    }
    catch (IscrizioneException exc) {

    System.out.println (exc.toString());

    }

}

}



17. Utilizzo di un eccezione personalizzata

  • Un eccezione personalizzata permette di generare un messaggio di errore e di arrestare l’esecuzione del programma; tuttavia il precedente codice fa uso della condizione «if», che in effetti potrebbe gestire tranquillamente l’imprevisto anche senza lanciare l’eccezione (es. mostrando in output un semplice messaggio informativo).
  • La ragione per cui è preferibile utilizzare un eccezione personalizzata è quella di poter scindere i messaggi di imprevisto/errore dai regolari messaggi operativi; un’altra ragione è quella che riguarda il meccanismo di propagazione dell’eccezione, scopriremo questo automatismo nelle prossime slide attraverso due esempi concreti.


18. Primo esempio «errato»

[Download]

(Prima parte del codice)

// Eccezione personalizzata

class IscrizioneException extends Exception {

public IscrizioneException() {
    super("Impossibile iscriversi");
}
@Override
public String toString() {
    return getMessage() + ": posti esauriti.";
}

}



19. Primo esempio «errato»

[Download]

(Seconda parte del codice)

// Classe per la gestione del Club (può lanciare l’eccezione)
class Club {

int numeroIscritti = 0;
void iscriviti() {
    try {

    if (numeroIscritti == 500) {

    IscrizioneException exc = new IscrizioneException();
    throw exc;

    }
    numeroIscritti++;

    }

    catch (IscrizioneException exc) {

    System.out.println(exc.toString());

    }

}

}



20. Primo esempio «errato»

[Download]

(Terza parte del codice)

// Programma principale che permette le iscrizioni
public class Programma {

public static void main(String[] args) {
    Club viaggiSconto = new Club();
    int candidati = 501; /* I candidati sono superiori al limite autoimposto, verrà lanciata l’eccezione */
    for (int i = 1; i <= candidati; i++) {

    viaggiSconto.iscriviti();
    System.out.println("Iscrizione n° " + i + " registrata correttamente.");

    }

}

}

[Risultato]


Iscrizione n° 490 registrata correttamente.
Iscrizione n° 491 registrata correttamente.
Iscrizione n° 492 registrata correttamente.
Iscrizione n° 493 registrata correttamente.
Iscrizione n° 494 registrata correttamente.
Iscrizione n° 495 registrata correttamente.
Iscrizione n° 496 registrata correttamente.
Iscrizione n° 497 registrata correttamente.
Iscrizione n° 498 registrata correttamente.
Iscrizione n° 499 registrata correttamente.
Iscrizione n° 500 registrata correttamente.
Impossibile iscriversi: posti esauriti.
Iscrizione n° 501 registrata correttamente.

Notare che la gestione dell’eccezione all’interno delle classe «Club()» rappresenta una complicazione per il programma principale che non sospende la sua esecuzione.



21. Secondo esempio «corretto»

[Download]

(Prima parte del codice)

// Eccezione personalizzata

class IscrizioneException extends Exception {

public IscrizioneException() {
    super("Impossibile iscriversi");
}
@Override
public String toString() {
    return getMessage() + ": posti esauriti.";
}

}



22. Secondo esempio «corretto»

[Download]

(Seconda parte del codice)

// Classe per la gestione del Club (può lanciare l’eccezione)
class Club {

int numeroIscritti = 0;
void iscriviti() throws IscrizioneException {
    if (numeroIscritti == 500) {

    IscrizioneException exc = new IscrizioneException();
    throw exc;

    }
    numeroIscritti++;
}

}

Il compilatore grazie alla parola chiave «throws», sarà a conoscenza che «IscrizioneException» verrà gestita da un’altra parte, per questo motivo è stato possibile rimuovere il blocco «try-catch».



23. Secondo esempio «corretto»

[Download]

(Terza parte del codice)

// Programma principale che permette le iscrizioni
public class Programma {

public static void main(String[] args) {
    Club viaggiSconto = new Club();
    int candidati = 501;   /* I candidati sono superiori al limite autoimposto, verrà lanciata l’eccezione */
    for (int i = 1; i <= candidati; i++) {

    try {

    viaggiSconto.iscriviti();
    System.out.println ("Iscrizione n° " + i + " registrata correttamente.");

    }
    catch (IscrizioneException exc) {

    System.out.println (exc.toString());

    }

    }

}

}

[Risultato]


Iscrizione n° 490 registrata correttamente.
Iscrizione n° 491 registrata correttamente.
Iscrizione n° 492 registrata correttamente.
Iscrizione n° 493 registrata correttamente.
Iscrizione n° 494 registrata correttamente.
Iscrizione n° 495 registrata correttamente.
Iscrizione n° 496 registrata correttamente.
Iscrizione n° 497 registrata correttamente.
Iscrizione n° 498 registrata correttamente.
Iscrizione n° 499 registrata correttamente.
Iscrizione n° 500 registrata correttamente.
Impossibile iscriversi: posti esauriti.

In questo caso sappiamo che il metodo «Iscriviti()» potrebbe lanciare l’eccezione, ma sappiamo anche che essa non viene gestita; per questo motivo è necessario che la chiamata al metodo (con relativo output) sia circoscritta in un blocco «try-catch».



24. Considerazioni sugli esempi

  • La differenza principale che intercorre tra il primo ed il secondo esempio è quella che:
    - nel primo caso non viene usato il meccanismo di propagazione dell’eccezione, di conseguenza non sarà possibile l’arresto di tutte le attività del software relative al metodo su cui si è verificato l’imprevisto;
    - mentre nel secondo esempio, l’uso della parola chiave «throws», consente di gestire l’eccezione in modo da interrompere tutte le attività necessarie.


25. Uso avanzato di «throws»

  • E’ possibile dichiarare nella clausola «throws» anche più di una eccezione, separando le varie tipologie con virgola, come nel seguente esempio:

void iscriviti() throws IscrizioneException, ArithmeticException {
                …
}

  • Chiaramente anche in questo caso sarà necessario gestire le due eccezioni nel metodo chiamante tramite blocco «try-catch», scrivendo un qualcosa del tipo:

try {      
                …
                ogg.iscriviti();
                …
}

catch (IscrizioneException exc) {
                …
}
catch (ArithmeticException exc) {
                …
}



26. Introduzione alle asserzioni

  • Un’asserzione è una istruzione che permette di provare alcuni comportamenti del nostro software, in definitiva trattasi di una semplice attività di debug via codice.
  • Ogni asserzione richiede che sia verificata una espressione di tipo booleano; se la verifica è negativa, significa che siamo in presenza di un bug.
  • Inoltre il programmatore in fase di rilascio del software, può decidere di disabilitare la lettura delle asserzioni, in modo tale da rendere più veloce l’esecuzione del definitivo programma.


27. Sintassi di una asserzione

  • Per dichiarare un’asserzione è necessario usare la parola chiave «assert», seguita da un’espressione che restituisce un valore di tipo booleano (vero o falso), ad esempio:

assert eta>0;

se l’espressione è «vera» il programma prosegue normalmente, se il valore restituito dall’espressione è «falso» viene lanciata un’«AssertionError».

  • Un’«AssertionError» è una classe che dipende dalla classe principale «Error», questo significa che in caso di valore «falso» non viene emessa un’eccezione, ma un viene mostrato un errore.


28. Sintassi di una asserzione

  • Tuttavia è necessario comprendere che al momento di dichiarare un’asserzione, è possibile decidere di stampare a video un’messaggio esplicativo anziché visualizzare solo un’«AssertionError»; ad esempio in questo caso:

assert eta>0: "l’età non può essere negativa";

se l’espressione restituisse valore «falso», verrebbe visualizzato pure il messaggio «l’età non può essere negativa»; mentre se scrivessimo in questo modo:

assert eta>0: "l’età non può essere pari a" + eta;

verrebbe mostrata anche la variabile incriminata.



29. Compilare ed eseguire un programma con delle asserzioni

  • Per compilare un programma che presenta all’interno del suo codice la parola chiave «assert», è necessario eseguire il comando
    «javac –source 7 Programma.java».
  • Nel caso si volesse sfruttare «assert» come identificatore rinunciando all’automatismo delle asserzioni,  bisognerà scrivere «javac –source 1.3 Programma.java», perché nelle versioni precedenti alla 1.4 «assert» non è parola chiave.
  • Mentre per eseguire un programma con asserzioni abilitate è necessario eseguire il comando «java –ea Programma», se scrivessimo «java –da Programma» o «java Programma» verrebbe eseguito il software con asserzioni disabilitate.


30. Forzare l’abilitazione delle asserzioni

  • Per assicurarsi che le asserzioni siano sempre abilitate in fase di esecuzione del software, è opportuno inserire questo inizializzatore statico di controllo:


static {

boolean asserzioniAbilitate = false;
assert  asserzioniAbilitate = true;
if (!asserzioneAbilitate) {

RuntimeException exc = new RuntimeException();
throw exc;

}

}

In questo modo se le asserzioni dovessero risultare disabilitate verrà lanciata una «RuntimeException».