Menu

Optional in Java: evitare null e NullPointerException

Cos'è java.util.Optional, come crearne uno e come leggerne il valore in sicurezza con map, filter, orElse e ifPresent invece dei controlli su null.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Il problema che Optional risolve

In Java, un metodo che potrebbe non avere una risposta ha sempre avuto una scelta scomoda: restituire null e sperare che il chiamante si ricordi di controllarlo. Di solito non succede, e il risultato è una NullPointerException che compare lontano dal metodo che ha restituito null.

Optional<T> è una piccola scatola che contiene un valore oppure è esplicitamente vuota. Restituendo Optional<User> invece di User, un metodo dice ai chiamanti "potrei non trovare niente" direttamente nella sua firma, e il compilatore li spinge a gestire quel caso.

Creare un Optional

Ci sono tre metodi factory, e scegliere quello giusto è importante:

  • Optional.of(value): il valore non deve essere null, altrimenti lancia subito una NullPointerException.
  • Optional.ofNullable(value): vuoto se il valore è null, pieno negli altri casi. Usalo per incapsulare qualcosa che potrebbe essere null.
  • Optional.empty(): un optional vuoto.

Un errore comune è usare Optional.of(x) con un valore che potrebbe essere null: così vanifichi lo scopo e lanci proprio l'eccezione che volevi evitare. Nel dubbio, usa ofNullable.

Leggere il valore in sicurezza

Una volta che hai un optional, l'obiettivo è estrarre il valore senza dare per scontato che ci sia. Lo strumento grezzo è get(), e non dovresti quasi mai usarlo:

Optional<String> name = Optional.empty();
String value = name.get();   // lancia NoSuchElementException: una NPE travestita

Invece, fornisci un valore di ripiego o reagisci alla presenza del valore. orElse restituisce un valore predefinito quando l'optional è vuoto; ifPresent esegue il codice solo quando esiste un valore:

Usa orElseGet(supplier) invece di orElse quando il valore predefinito è costoso da costruire: il supplier viene eseguito solo se l'optional è davvero vuoto. L'argomento di orElse viene sempre valutato, anche quando non serve.

orElseThrow per "questo deve esistere"

A volte un optional vuoto è davvero un errore: un valore di configurazione obbligatorio, un utente che dovrebbe essere nel database. orElseThrow trasforma un optional vuoto in un'eccezione chiara a tua scelta:

Si legge molto meglio di un blocco if (opt.isPresent()) e rende esplicito l'errore nel punto in cui si verifica.

Trasformare con map e filter

Il vero vantaggio è il concatenamento. map applica una funzione al valore solo se presente e lascia vuoto un optional vuoto, quindi trasformi i dati senza mai toccare un null. filter scarta il valore se non supera un test.

Se la tua funzione di mapping restituisce a sua volta un Optional, usa flatMap invece di map per evitare uno scomodo Optional<Optional<T>>. È la stessa distinzione tra map e flatMap che hai visto con gli stream: un Optional si comporta come uno stream di zero o un elemento.

Dove usare Optional (e dove no)

Optional è pensato come tipo di ritorno per metodi che possono legittimamente non produrre un risultato: gli esempi classici sono Stream.findFirst(), le ricerche in stile Map e il parsing. Usalo lì e la tua API documenterà da sola i suoi vuoti.

Non è pensato per:

  • Campi. Non è serializzabile e aggiunge un oggetto per ogni campo. Usa riferimenti normali e valida i valori nel costruttore.
  • Parametri dei metodi. I chiamanti dovrebbero incapsulare gli argomenti in Optional.of(...), che è più verboso di un overload o di un parametro che accetta null.
  • Collezioni. Restituisci una List vuota, non un Optional<List>. Una collezione vuota significa già "niente".

Usato come tipo di ritorno, Optional trasforma i bug silenziosi dovuti a null in inviti, già in fase di compilazione, a gestire il caso mancante.

Prossima pagina: eccezioni

Optional gestisce in modo pulito il caso quotidiano in cui "potrebbe non esserci un valore", ma alcuni errori sono davvero eccezionali: un file che non si apre, una rete che cade, un input che non si riesce a interpretare. Java li rappresenta con le eccezioni, ed è l'argomento della prossima pagina.

Domande frequenti

Cos'è Optional in Java e perché usarlo?

Optional<T> è un contenitore che contiene un valore oppure è vuoto. Esiste per rendere esplicito nel tipo di ritorno di un metodo che "potrebbe non esserci un valore", così chi lo chiama è spinto a gestire il caso vuoto invece di dimenticare un controllo su null e incappare in una NullPointerException a runtime. Usalo soprattutto come tipo di ritorno per metodi che potrebbero non produrre un risultato, come una ricerca che non trova niente.

Che differenza c'è tra Optional.of e Optional.ofNullable?

Optional.of(x) lancia subito una NullPointerException se x è null: usalo quando sai che il valore non è null. Optional.ofNullable(x) restituisce un Optional vuoto quando x è null e uno pieno negli altri casi: usalo quando il valore potrebbe essere null. Optional.empty() ti dà direttamente un optional vuoto.

Devo chiamare Optional.get() per leggere il valore?

Evita il semplice get(): lancia NoSuchElementException se l'optional è vuoto, che è solo una NullPointerException con un altro nome. Preferisci orElse, orElseGet, orElseThrow, ifPresent o map, così il caso vuoto è sempre gestito. Se proprio devi controllare prima, proteggiti con isPresent(), ma i metodi funzionali sono più puliti.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA