back to top

Java: cos’è l’errore NullPointerException e come risolverlo

NullPointerException è un errore comune in Java, generato quando un programma tenta di utilizzare una referenza a un oggetto che ha null come valore. In questo articolo, esploreremo cosa sia NullPointerException, perché si verifica, come prevenire questo errore, e presenteremo esempi pratici per comprendere meglio il problema. Ciò sarà utile per chiunque stia imparando Java o lavori con questo linguaggio di programmazione.

RuntimeException è una superclasse di Java che ricomprende tutte le eccezioni che possono essere lanciate nel corso della normale operatività della JVM (Java Virtual Machine).

Pubblicità

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.

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. Inoltre, è buona prassi controllare che un oggetto non sia null prima di invocarvi metodi o accedervi attributi.

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 NullPointerException dovrebbe essere evitata o gestita in fase di produzione ma può risultare utile per finalità di test. Si pensi, per esempio, a tutti i 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.

Altri contenuti interessanti

Pubblicità
Claudio Garau
Claudio Garau
Web developer, programmatore, Database Administrator, Linux Admin, docente e copywriter specializzato in contenuti sulle tecnologie orientate a Web, mobile, Cybersecurity e Digital Marketing per sviluppatori, PA e imprese.

Leggi anche...

Vibe Coding: cos’è, come funziona e quali sono i migliori strumenti AI per programmare

Immagina di poter scrivere software senza dover digitare una...

I migliori libri per imparare a programmare in Python

Imparare a programmare in Python è un passo fondamentale...

Il file manifest.json: cos’è e a cosa serve

Il file manifest.json è un componente chiave nelle applicazioni web moderne,...

Java: cos’è e a cosa serve l’operatore modulo (%)

In Java, l'operatore modulo è rappresentato dal simbolo "%"...

Radice quadrata in C: vediamo come calcolarla in diversi modi

La radice quadrata è un'operazione matematica piuttosto comune (in...

Sperimentare la sequenza di Collatz in C++

Vediamo come verificare la congettura di Collatz con C++....
Pubblicità