13. Le eccezioni in Java
Andrea Pastore 19/03/2020 0
Quando creiamo un programma anche se la sintassi è corretta non significa che sia tutto perfettamente funzionante, possono verificarsi errori durante l'esecuzione che java non è stato in grado di rilevare in fase di compilazione.
Questo tipo di errori si definisce "a Runtime", perché avvengono durante l'esecuzione. Quando si verificano questi errori Java lancia un'eccezione, che termina immediatamente il programma. Vediamo un esempio: le due righe di codice sottostanti danno errore in esecuzione, perché la posizione 15 non esiste nel nostro array. Si tratta di un errore che non può essere individuato quando si scrive il codice perché la sintassi è corretta: stiamo semplicemente chiedendo a java di darci la stringa presente ad una posizione. Solo al momento dell’esecuzione Java si accorgerà che l’array ha solo 10 elementi e non potrà fornire la posizione 15, per cui lancerà l’eccezione che terminerà il programma in maniera inaspettata.
public class HelloException {
public static void main(String[] args) {
String[] arrayString = new String[10];
System.out.println(arrayString[15]);
}
}
Questo codice genererà un’eccezione di tipo ArrayIndexOutOfBoundsException. Questa eccezione indica proprio che abbiamo chiesto una posizione non presente nell’array.
Anatomia di un’eccezione
Le eccezioni sono classi Java che estendono la classe Exception, hanno sempre un nome che contiene la parola Exception e altre parole che indicano la natura del problema.
Lo stack trace di un’eccezione
Quando si verifica un’eccezione Java stampa la riga di codice che ha generato quell’ecezione, con tutte le chiamate precedenti. Questo è molto utile per capire la sorgente dell’errore. Si veda l’immagine sottostante:
In questa immagine viene mostrato uno stack trace causato da un NullPointerException. L’ultima chiamata è in basso (difatti è nella classe Main che è la classe che fa partire i programmi Java), poi viene ripercorsa la storia delle chiamate: dalla classe Main si arriva alla classe GestoreOrdini e qui viene indicato il metodo che causa l’eccezione.
Vediamo alcune eccezioni comuni, che troveremo frequentemente nel nostro futuro lavoro:
Nome | Funzionalità |
---|---|
NullPointerException | Compare quando stiamo cercando di accedere ad una risorsa che, al momento, ha valore null |
ArrayIndexOutOfBoundsException | Compare quando stiamo provando ad accedere ad una posizione dell’array che non esiste (ad esempio quando l’array ha 10 elementi e noi proviamo ad accedere alla posizione 15. |
IOException | Comapre quando java non riesce ad eseguire operazioni di input/output (ad esempio la scrittura di un file). |
Nota: gli errori a Runtime non dipendono sempre da una nostra disattenzione, ma potrebbero essere causati da una causa esterna. Quello che dobbiamo fare, però, è gestirle, in modo che il nostro programma sappia sempre come agire.
Gestire un’eccezione
Per gestire un’eccezione abbiamo in realtà due metodi: il primo è il costrutto try ... catch. Il secondo è la parola chiave throws, che può essere utilizzata quando si vuole posporre la gestione dell’eccezione. I più importanti IDE (quindi anche Eclipse, quello consigliato in questo libro) consentono di scegliere tra i due metodi di gestione dell’eccezione. Osserviamo ora le differenze.
Il costrutto try ... catch
All’interno del try deve essere inserito il codice che può generare un’eccezione, mentre all’interno del catch viene inserito il codice per gestire l’eccezione quando questa si verifica. Riscriviamo il codice visto prima in modo che l’eccezione venga gestita:
public class HelloException {
public static void main(String[] args) {
String[] arrayString = new String[10];
try {
System.out.println(arrayString[15]);
}
catch(Exception e) {
System.out.println("si è verificata un'eccezione");
}
}
}
Quando Java eseguirà questo codice si accorgerà nuovamente che non esiste la posizione 15, ma essendoci un costrutto catch ha le istruzioni su come comportarsi, quindi eseguirà il codice contenuto in esso e continuerà l’esecuzione del programma. Nel catch dobbiamo indicare l’eccezione che vogliamo catturare: nell’esempio sopra abbiamo passato come parametro Exception e, il che significa che verranno considerate tutte le eccezioni: qualsiasi eccezione si verificherà verrà eseguito sempre lo stesso codice (in questo caso verrà stampata la frase “Si è verificata un’eccezione”). È possibile specificare azioni diverse da intraprendere a seconda dell’eccezione che si verifica aggiungendo più clausole di catch. Si veda il codice sotto, che dopo il try presenta due clausole catch, che catturano ognuna un’eccezione diversa:
public class HelloException {
public static void main(String[] args) {
String[] arrayString = new String[10];
try {
System.out.println(arrayString[15]);
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("si è verificata un'eccezione ArrayIndexOutOfBoundsException");
}
catch(NullPointerException e) {
System.out.println("si è verificata un'eccezione NullPointerException");
}
}
}
Non c’è limite al numero di eccezioni che possiamo catturare, e questa caratteristica ci consente di eseguire le giuste operazioni ad ogni eccezione.
La parola chiave throws
Ci sono casi in cui non vorremmo gestire le eccezioni nel metodo in cui si possono verificare, perché per qualche motivo può esserci più comodo gestirle in altre parti del codice. In questi casi si può usare la parola chiave throws seguita dal nome dell'eccezione.
L'eccezione non è gestita, ma Java considererà il metodo corretto e richiederà la gestione dell'eccezione ogni volta che quel metodo verrà invocato.
Potrebbero interessarti anche...
Andrea Pastore 19/03/2020
10. Strutture di dati
Finora abbiamo visto solo tipi di dati semplici: int, double, boolean e cosi via. Tuttavia i programmi reali devono elaborare grandi quantità di dati, e sarebbe scomodo e limitante memorizzarli uno per volta. Per questo motivo già dagli anni Ottanta sono state create le strutture di dati, tipi di variabili più elaborati che possono contenere al loro interno delle variabili.
Gli array
La struttura dati più semplice è l’array, che consente di memorizzare un numero stabilito di elementi di un determinato tipo. Ad esempio la riga sottostante crea un array di 10 numeri interi
int[] arrayInt = new int[10];
se non conosciamo la dimensione dell’array (perché magari ci è stato passato in input da un programma che non abbiamo realizzato noi) possiamo recuperarla con la proprietà length:
int arraySize = arrayInt.length;
Se sono noti tutti gli elementi che dobbiamo inserire nell’array possiamo anche inizializzarlo in questo modo:
int[] array2 = {2, 5, 6, 7, 11};
Essendo l’array un contenitore di variabili, quando lo utilizziamo dobbiamo specificare a quale variabile vogliamo accedere, ad esempio la seguente riga di codice:
System.out.println(arrayInt[5]);
Stampa l’elemento alla posizione 5 dell’array.
Nota: la prima posizione di un array non è 1, ma 0; quindi stampare la quinta posizione significa in realtà stampare il sesto elemento dell’array.
Frequentemente capita di dover elaborare in sequenza tutti gli elementi di un array, in questo caso possiamo utilizzare un ciclo for. Ad esempio il ciclo for sottostante inizializza tutti gli elementi dell’array creato precedentemente al valore 5.
for(int i=0;i<arraySize;i++) {
arrayInt[i] = 5;
}
Notiamo che nel ciclo abbiamo usato una variabile arraySize dove è memorizzata la dimensione dell’array. Questa variabile è stata creata qualche riga sopra. È anche possibile scrivere il ciclo for in questo modo:
for(int i=0;i<arrayInt.length;i++) {
arrayInt[i] = 5;
}
in questo modo evitiamo di scrivere la variabile esterna, ma stiamo sprecando tempo di calcolo, perché ad ogni ciclo Java dovrà calcolare la dimensione dell’array.
Andrea Pastore 19/03/2020
3. Le variabili in Java
Una variabile è un contenitore di dati che risiede nella memoria ram del computer. Di ogni variabile possiamo stabilire il nome e cosa deve contenere. Quando dobbiamo utilizzare quel valore lo richiamiamo.
Tipi di dati
Java è un linguaggio tipizzato, significa che per ogni variabile dobbiamo sempre specificare il tipo di dato. In Java ci sono una serie di tipi di dati elementari che sono riportati nella tabella sottostante:
Tipo di dato | Spiegazione |
---|---|
int | serve per memorizzare numeri interi |
double | serve per memorizzare numeri decimali. Attenzione, per separare i numeri decimali in Java si usa il punto e non la virgola |
String | serve per memorizzare il testo, dobbiamo inserire le virgolette |
boolean | tipo di dato che può avere due valori: true o false. Viene usato molto quando dobbiamo prendere decisioni |
Scriviamo un programma che crea delle variabili:
public class HelloDanilo {
public static void main(String[] args) {
System.out.println("Stampo una stringa");
int a = 10;
int b = 20;
int addizione = a + b;
System.out.println(addizione);
}
}
Innanzitutto vediamo che anche questo programma come prima operazione stampa una stringa ( System.out.println("Stampo una stringa"); ). Successivamente vediamo che crea delle variabili intere:
int a = 10;
int b = 20;
int addizione = a + b;
notiamo una cosa: alle prime due variabili sto assegnando un numero, alla terza invece sto chiedendo a Java di fare un’addizione delle due variabili che ho creato in precedenza. Altra cosa da notare con le variabili è che utilizzo l’operatore = per dare un valore alla variabile, questa operazione si chiama assegnamento.
L’ultimo comando di questo programma stampa la variabile addizione:
System.out.println(addizione);
notiamo una cosa: la parola addizione non contiene le virgolette. Questo perché stiamo chiedendo a Java di stampare una variabile. Quando Java interpreta questo codice sostituirà la parola addizione con il valore corrispondente, ed eseguirà la stampa. Se invece avessimo usato le virgolette, ovvero avremmo scritto:
System.out.println(“addizione”);
Java avrebbe considerato la parola come un testo da stampare e quindi avrebbe stampato “addizione”.
Convertire variabili da un tipo di dato ad un altro
Può capitare di dover convertire variabili in un altro formato, ad esempio da stringa a numero intero oppure da stringa a un numero decimale. Per fare questo ci sono dei comandi appositi. Supponiamo di avere queste variabili:
String s1 = "10";
String s2 = "5.4";
Per convertire la stringa s1 in un numero intero bisogna usare questo comando:
int numero1 = Integer.parseInt(s1);
Per convertire la stringa s2 in un numero decimale bisogna usare questo comando:
double numero2 = Double.parseDouble(s2);
Nota: se il tipo di dato non è corretto (ad esempio provo a fare parseInt su s2, che essendo un numero decimale ha un punto) questi comandi genereranno un’eccezione.
Assegnare i nomi alle variabili
I nomi delle variabili, se scelti con criterio, possono aiutare a capire il motivo per cui sono state create. Osserviamo questo codice
int var1 = 10;
Nel momento in cui lo creiamo, ovviamente ci ricordiamo cosa va. Ma quando ci capiterà di dover apportare modifiche a quel codice a distanza di molto tempo non è detto che ci ricordiamo per quale motivo avevamo creato quella variabile. Certo lo potremmo capire andando a vedere il codice successivo, ma perderemmo del tempo. Per evitare ciò basta chiamare le variabili con nomi autoesplicativi, ad esempio:
int anniUtente = 10;
in questo caso sarà facile capire cosa fa quella variabile, perché il suo nome ci spiega cosa fa.
Operatori aritmetici
Sulle variabili numeriche possono essere eseguite le operazioni aritmetiche, ad esempio:
int b = a + 10;
Oltre alle 4 operazioni a cui siamo abituati Java ci mette a disposizione anche l’operatore modulo, per calcolare il modulo di un numero intero. Ecco la tabella degli operatori aritmetici:
Operatore | Spiegazione |
---|---|
+ | Operatore che consente di sommare due variabili: es a + b; |
- | Operatore che consente di sottrarre due variabili: es a - b; |
* | Operatore che consente di sottrarre due variabili: es a - b; |
/ | Operatore che consente di sottrarre due variabili: es a / b; |
% | Operatore che consente di eseguire l’operazione modulo, es a % b |
Operatori di assegnamento
Quando noi creiamo una nuova variabile possiamo assegnare un valore iniziale, ad esempio scrivendo:
int a = 10;
L’assegnamento si fa con l’operatore “=”. Negli esempi sopra abbiamo sempre assegnato un valore ad una variabile appena creata, ma potremmo anche non farlo e scrivere semplicemente:
int a;
In questo secondo caso la variabile viene inizializzata o con un valore di default (come ad esempio nel caso dei numeri interi che vengono inizializzati a 0) o a null (tipi di dati più complessi che vedremo in seguito).
Altri operatori di assegnamento
Java mette a disposizione altri operatori di assegnamento, che non utilizziamo al momento della creazione di una variabile ma in seguito. Si tratta di scorciatoie che Java ci mette a disposizione. Il comando e l’azione svolta sono indicati nella tabella sottostante.
Operatore | Azione svolta |
---|---|
+= | è una scorciatoia per incrementare una variabile di una certa quantità equivale a scrivere: totale = totale + 10; |
-= | è una scorciatoia per sottrarre a una variabile una certa quantità equivale a scrivere: totale = totale - 10; |
*= | è una scorciatoia per moltiplicare una variabile per un numero equivale a scrivere: totale = totale * 10; |
Assegnare variabili casuali
Java, come tanti altri linguaggi di programmazione, è in grado di generare variabili casuali, grazie ad una classe chiamata Random. Le variabili generate in questo modo non sono in realtà totalmente casuali perché sono generate da un algoritmo, ma garantisce comunque una certa varietà di risultati.
La prima cosa da fare è importare una classe di utility che mette a disposizione Java, la classe Random:
import java.util.Random;
Nel nostro codice poi dobbiamo creare un oggetto (più avanti capiremo meglio cos’è) di tipo Random con questa riga di codice:
Random generatore = new Random();
una volta creato questo oggetto possiamo creare diversi tipi di variabili random: numeri interi con questo codice:
int rand = generatore.nextInt(2);
numeri decimali con quest’altro codice:
double rand = generatore.nextDouble();
possiamo genereare anche una variabile di tipo booleano, con questo codice:
boolean rand = generatore.nextBoolean();
Ecco il codice completo per creare una variabile casuale:
import java.util.Random;
public class HelloRandom {
public static void main() {
Random generatore = new Random();
int rand = generatore.nextInt(2);
// fa qualcosa col numero appena creato
}
}
Andrea Pastore 19/03/2020
5. Il costrutto if
Il costrutto if serve per far prendere decisioni ai nostri programmi. Inizia con la parola chiave if seguita dalle parentesi tonde. Se la condizione è verificata viene eseguito il codice contenuto all’interno delle parentesi graffe. Possiamo dare a Java anche istruzioni su cosa fare nel caso in cui la condizione non sia verificata, scrivendo dopo l’if un else.
if(i<5) {
Eseguo un certo codice
}
else {
Eseguo un altro codice
}
La condizione che abbiamo posto è che i sia minore di 5, il simbolo < è definito operatore. Più avanti vedremo molti altri operatori utilizzabili.
La parola chiave else non è obbligatoria, possiamo scegliere di eseguire un codice solo se una condizione è verificata e non fare nulla in caso contrario. Vediamo alcuni esempi, supponiamo di avere una variabile int i = 5:
if(i<5) {
System.out.println("i minore di 5");
}
Questo primo codice controlla che i sia minore di 5, se questa condizione è vera allora viene eseguito il codice al suo interno (in questo caso System.out.println("i minore di 5"); ). Noi abbiamo supposto che i abbia valore 5, quindi in questo caso Java rileverebbe che la condizione non è vera e non stamperebbe nulla, terminando il programma senza dare informazioni. Vediamo il codice sottostante:
if(i<5) {
System.out.println("i minore di 5");
}
else {
System.out.println("i non è minore di 5");
}
in questo codice oltre all’if c’è un else: quindi Java rileverà ugualmente che la condizione nell’if non è vera, ed eseguirà il codice presente nell’else, in questo caso la stampa del messaggio “i non è minore di 5”.
Le diverse condizioni possibili nell’if
All’interno dell’if possiamo porre diverse condizioni: ad esempio possiamo chiederci se un numero è maggiore, minore, uguale oppure diverso da un altro numero. Per i primi due casi si usano gli operatori maggiore e minore, mentre per verificare l’uguaglianza si usa l’operatore ==. Abbiamo bisogno di due uguali perché abbiamo visto che un singolo uguale serve per inizializzare una variabile (l’assegnamento).
if(i==5) {
System.out.println("i uguale a 5");
}
In questo caso Java eseguirà il codice all’interno dell’if se e solo se i è uguale a 5.
Per verificare che un numero sia diverso da un altro si usa l’operatore !=
if(i!=5) {
System.out.println("i diverso da 5");
}
In questo caso Java eseguirà il codice all’interno dell’if se e solo se i è diverso da 5. Se dobbiamo verificare che un numero sia minore oppure uguale ad un altro, oppure maggiore o uguale ad un altro, possiamo usare l’operatore <= oppure >=. Ad esempio in questo caso:
if(i<=5) {
System.out.println("i è minore o uguale a 5");
}
Java eseguirà il codice all’interno dell’if solo se i è minore o uguale di 5 (quindi a differenza del semplice simbolo < i può valere anche 5 e la condizione è sempre rispettata)
Condizioni più elaborate con gli operatori AND e OR
Finora abbiamo visto che la condizione all’interno dell’if verificano il valore di una sola variabile, non è sempre cosi. Possiamo trovarci a dover verificare più variabili con lo stesso if, ad esempio potremmo dover verificare che un determinato utente sia maggiorenne e si chiami Mario.
Potremmo avere ancora un altro caso: potremmo dover verificare che solo una delle due condizioni sia vera, per rifarci all’esempio di prima potremmo dover verificare che l’utente si chiami Mario oppure che sia maggiorenne.
È possibile fare queste due richieste con gli operatori AND e OR, rappresentati in Java dai simboli && per l’AND e || per l’OR. L’operatore AND restituisce true quando entrambe le condizioni sono vere, l’operatore OR restituisce true se almeno una delle due condizioni è vera.
Facciamo un esempio con il primo caso, ovvero scriviamo il codice per verificare che un utente sia maggiorenne e si chiami e si chiami Mario. Per verificare questa condizione dobbiamo usare l’operatore AND. Assumiamo di avere una variabile età e una variabile nome, il codice sarà:
if(età>=18 && nome.equals(“Mario”)) {
eseguo il codice
}
Vediamo ora l’altra possibilità, ovvero quando ci accontentiamo che solo una delle due condizioni sia vera. In questo caso dobbiamo usare l’operatore OR:
if(età>=18 || nome.equals(“Mario”)) {
eseguo il codice
}
Nel caso in cui volessimo ignorare eventuali differenze tra maiuscole e minuscole nella variabile nome Java mette a disposizione il metodo equalsIgnoreCase:
if(età>=18 && nome.equalsIgnoreCase (“Mario”)) {
eseguo il codice
}
oppure nel caso in cui dobbiamo verificare che solo una delle due condizioni sia vera
if(età>=18 || nome.equalsIgnoreCase (“Mario”)) {
eseguo il codice
}