RuntimeException รจ una superclasse di Java che ricomprende tutte le eccezioni che possono essere lanciate nel corso della normale operativitร della JVM (Java Virtual Machine).
Nei linguaggi di programmazione orientati agli oggetti, come appunto Java, una superclasse รจ una classe generica che puรฒ essere estesa in sottoclassi, altrimenti dette classi figlie, che hanno il compito di fornire delle versioni specializzate delle classi principali da cui derivano. Nel caso specifico di RuntimeException e delle sue sottoclassi si parla nello specifico di unchecked exceptions, cioรจ di espressioni che non รจ necessario dichiarare in un metodo e che si manifestano automaticamente in fase di runtime quando si verificano determinate condizioni.
Indice
Quando si verifica l’errore NullPointerException?
Un caso abbastanza frequente di RuntimeException รจ rappresentato dalla sottoclasse chiamata NullPointerException (o piรน estesamente java.lang.NullPointerException) corrispondente ad unโeccezione che viene lanciata quando unโapplicazione tenta di utilizzare la referenza di un oggetto che ha null come valore, proprio per questo motivo parliamo di โeccezione a puntatore nulloโ.
Variabili, oggetti e puntatori
A livello pratico un oggetto รจ sostanzialmente unโentitร associata ad una variabile di riferimento, nello stesso tempo รจ possibile affermare che la dichiarazione di questโultima determina la creazione di un puntatore verso lโoggetto a cui si riferisce. Per comprendere il funzionamento di questo meccanismo รจ possibile proporre lโesempio di una comune variabile di tipo intero a cui successivamente viene associato un valore:
int numero;
numero = 5;
Abbiamo quindi una primitiva, denominata numero, che come anticipato viene dichiarata come un intero a cui Java attribuisce il valore 0 in fase di inizializzazione. Nel momento in cui a numero viene dato un valore specifico (5 nel codice proposto) esso viene archiviato nella porzione di memoria che rappresenta il riferimento di numero.
Ora l’esempio presentato puรฒ essere riproposto nel modo seguente in cui invece di dichiarare una variabile associandola al suo tipo di dato si dichiara direttamente un tipo di riferimento:
Integer numero;
numero = new Integer(5);
Come รจ possibile notare la dichiarazione della variabile numero avviene ugualmente, la differenza rispetto al codice precedente sta invece nel fatto che ad essa non viene attribuito un valore al momento dellโinizializzazione. Questo accade perchรฉ in Java int e Integer non hanno lo stesso significato a livello sintattico, esattamente come accade per char e Character nel caso delle stringhe o a float e Float per i decimali.
int infatti non รจ una classe ma un tipo primitivo che opera come rappresentazione di unโinformazione utilizzabile per lโelaborazione di calcoli numerici, informazione che in pratica รจ un valore mutabile a runtime anche dopo la dichiarazione della variabile int.
Integer invece รจ una classe contenente un campo intero, motivo per il quale รจ possibile rappresentarla come un contenitore di un int (classe wrapper). A ciรฒ si aggiunga che il valore di Integer non รจ modificabile durante lโesecuzione di un programma e per potergli attribuire un nuovo valore รจ necessario ricorrere al costrutto new Integer(n) dove โnโ รจ un numero intero.
Ritornando al codice, nella prima riga a numero non viene associato un valore ma abbiamo comunque la generazione del puntatore in quanto Integer รจ un tipo di riferimento. Il puntatore perรฒ non dispone di alcuna coordinata per il puntamento e lโunica possibilitร per Java รจ quella di impostare il valore su null che, notoriamente, a livello semantico non corrisponde esattamente a 0 ma allโassenza di un valore e di conseguenza allโinesistenza di un oggetto.
Per evitare questa eventualitร viene richiamata la keyword new con cui istanziare un oggetto Integer reindirizzando il puntatore su questโultimo. Se invece si dichiara una variabile senza creare lโoggetto corrispondente, e si prova ad accedere alle informazioni della variabile stessa, si avrร una NullPointerException.
A tal proposito, un caso particolare รจ quello dellโautoboxing, la conversione automatica che il compilatore Java opera tra tipi primitivi e gli oggetti delle classi wrapper corrispondenti, come quando ci si trova davanti ad unโespressione di questo genere:
int numero = y
con cui potrebbe essere prodotta una NullPointerException nel caso in cui y sia Integer.
Prevenire unโeccezione a puntatore nullo
La NullPointerException si verifica quindi ogni volta che un programma tenta di utilizzare la referenza di un oggetto che ha null come valore, questo puรฒ accadere anche quando si prova ad invocare un metodo da un oggetto null (comunemente nel momento in cui si usa il โ.โ per la chiamata al metodo) oppure nel tentativo di accedere al campo di un oggetto null o di modificarlo.
Il modo migliore per prevenire unโeccezione a puntatore nullo รจ quello di assicurarsi che tutti gli oggetti vengano inizializzati correttamente prima del loro utilizzo, ad esempio prima della richiesta di un metodo o di un campo da un oggetto.
Sulla base di quanto detto osserviamo come il codice seguente non possa fare altro che produrre una NullPointerException:
// Invocazione di un metodo con restituzione di una NullPointerException
import java.io.*;
class RestituisciNullPointerException
{
public static void main (String[] args)
{
// inizializzazione di una variabile con valore null
String x = null;
// confronto per uguaglianza tramite equals()
try
{
if (x.equals("Una stringa a caso"))
System.out.print("Corrispondenza rilevata");
else
System.out.print("Corrispondenza mancante");
}
// restituzione del messaggio di errore in caso di eccezione
catch(NullPointerException e)
{
System.out.print("Attenzione, rilevata una NullPointerException");
}
}
}
Il metodo equals() consente di confrontare lโoggetto su cui viene invocato con un altro oggetto passato sotto forma di parametro, restituendo in output un valore booleano. Nel codice proposto la generazione del messaggio di errore โAttenzione, rilevata una NullPointerExceptionโ รจ dovuta al fatto che il metodo equals() viene richiamato a partire da un oggetto null (โx.equals(..)โ).
// Invocazione di un metodo senza NullPointerException
import java.io.*;
class NonRestituisciNullPointerException
{
public static void main (String[] args)
{
// inizializzazione di una variabile con valore null
String x = null;
// verifica sul valore di x
try
{
if ("Una stringa a caso".equals(x))
System.out.print("Corrispondenza rilevata");
else
System.out.print("Corrispondenza mancante");
}
// restituzione del messaggio di errore in caso di eccezione
catch(NullPointerException e)
{
System.out.print("Attenzione, rilevata una NullPointerException");
}
}
}
Lo scopo del programma mostrato in precedenza รจ quello di effettuare un confronto tra una variabile String e una literal, dove per literal si intende un valore costante che puรฒ essere assegnato ad una variabile. Le literal possono essere String o Enum, cioรจ un tipo enumerabile con cui vincolare il valore di una variabile ad uno specifico set di valori definito preventivamente in sede di programmazione.
Dopo aver compilato il codice, il programma restituisce lโoutput โCorrispondenza mancanteโ in fase di esecuzione perchรฉ questa volta equals() non viene invocato su un oggetto null ma su una literal utilizzata come termine di confronto con la variabile String.
In generale รจ possibile affermare che un’eventuale a NullPointerException dovrebbe essere evitata o gestita in fase di produzione ma puรฒ risultare utile per finalitร di test. Si pensi per esempio a tutti casi in cui viene passato come input un parametro null ma questโultimo non รจ un parametro valido per il metodo utilizzato. Se un metodo ha il compito di effettuare una qualche operazione a carico di un oggetto, lโeccezione a puntatore nullo consente di evidenziare errori di programmazione risultando congeniale per il debugging.