Le classi Optional in Java

Concetti Chiave
  • Le classi Optional, OptionalDouble, OptionalInt, e OptionalLong sono utilizzate per gestire valori che possono essere presenti o meno, evitando l'uso di riferimenti null.
  • La classe Optional fornisce metodi per verificare la presenza di un valore, ottenere il valore se presente, e fornire un valore predefinito quando non è presente un valore.
  • Le classi OptionalDouble, OptionalInt, e OptionalLong sono specifiche per i tipi primitivi e forniscono metodi simili a quelli di Optional.
  • Queste classi aiutano a prevenire le eccezioni di tipo NullPointerException e migliorano la leggibilità del codice.
  • Le classi implementano metodi come isPresent(), get(), orElse(), e map() per lavorare con i valori in modo sicuro e conciso.

Le classi Optional

A partire da JDK 8, la libreria standard di Java include le classi Optional, OptionalDouble, OptionalInt, e OptionalLong. Queste classi sono state introdotte per affrontare il problema della gestione dei valori che possono essere presenti o meno, evitando l'uso di riferimenti null che possono causare eccezioni di tipo NullPointerException.

In passato, normalmente si usava il valore null per indicare che nessun valore è presente. Tuttavia, questo può portare a eccezioni NullPointerException se viene fatto un tentativo di dereferenziare un riferimento null. Di conseguenza, erano necessari controlli frequenti per verificare se un valore null era presente per evitare di generare un'eccezione. Queste classi forniscono un modo migliore per gestire tali situazioni.

La prima e più generale di queste classi è Optional. Per questa ragione, è il focus principale di questa lezione. È mostrata qui:

class Optional<T>

Si tratta di una classe generica, infatti T specifica il tipo di valore memorizzato. È importante capire che un'istanza Optional può contenere un valore di tipo T o essere vuota. In altre parole, un oggetto Optional non contiene necessariamente un valore.

Optional non definisce alcun costruttore, ma definisce diversi metodi che permettono di lavorare con gli oggetti Optional. Ad esempio, puoi determinare se un valore è presente, ottenere il valore se è presente, ottenere un valore predefinito quando nessun valore è presente, e costruire un valore Optional.

I metodi Optional sono mostrati nella tabella seguente:

Metodo Descrizione
static <T> Optional<T> empty() Restituisce un oggetto per cui isPresent() restituisce false.
boolean equals(Object opzionale) Restituisce true se l'oggetto invocante è uguale a opzionale. Altrimenti, restituisce false.
Optional<T> filter(Predicate<? super T> condizione) Restituisce un'istanza Optional che contiene lo stesso valore dell'oggetto invocante se quel valore soddisfa condizione. Altrimenti, viene restituito un oggetto vuoto.
Optional<U> flatMap(Function<? super T, Optional<U>> funzioneMappa) Applica la funzione di mappatura specificata da funzioneMappa all'oggetto invocante se quell'oggetto contiene un valore e restituisce il risultato. Restituisce un oggetto vuoto altrimenti.
T get() Restituisce il valore nell'oggetto invocante. Tuttavia, se nessun valore è presente, viene lanciata NoSuchElementException.
int hashCode() Restituisce un codice hash per il valore nell'oggetto invocante. Restituisce 0 se non c'è valore.
void ifPresent(Consumer<? super T> funz) Chiama funz se un valore è presente nell'oggetto invocante, passando l'oggetto a funz. Se nessun valore è presente, non avviene alcuna azione.
void ifPresentOrElse(Consumer<? super T> funz, Runnable seVuoto) Chiama funz se un valore è presente nell'oggetto invocante, passando l'oggetto a funz. Se nessun valore è presente, seVuoto verrà eseguito.
boolean isEmpty() Restituisce true se l'oggetto invocante non contiene un valore. Restituisce false se un valore è presente.
boolean isPresent() Restituisce true se l'oggetto invocante contiene un valore. Restituisce false se nessun valore è presente.
U Optional<U> map(Function<? super T, ? extends U> funzioneMappa) Applica la funzione di mappatura specificata da funzioneMappa all'oggetto invocante se quell'oggetto contiene un valore e restituisce il risultato. Restituisce un oggetto vuoto altrimenti.
static <T> Optional<T> of(T val) Crea un'istanza Optional che contiene val e restituisce il risultato. Il valore di val non deve essere null.
static <T> Optional<T> ofNullable(T val) Crea un'istanza Optional che contiene val e restituisce il risultato. Tuttavia, se val è null, allora viene restituita un'istanza Optional vuota.
Optional<T> or(Supplier<? extends Optional<? extends T>> funz) Se nessun valore è presente nell'oggetto invocante, chiama funz per costruire e restituire un'istanza Optional che contiene un valore. Altrimenti, restituisce un'istanza Optional che contiene il valore dell'oggetto invocante.
T orElse(T valPredefinito) Se l'oggetto invocante contiene un valore, il valore viene restituito. Altrimenti, viene restituito il valore specificato da valPredefinito.
T orElseGet(Supplier<? extends T> ottieniFunz) Se l'oggetto invocante contiene un valore, il valore viene restituito. Altrimenti, viene restituito il valore ottenuto da ottieniFunz.
T orElseThrow() Restituisce il valore nell'oggetto invocante. Tuttavia, se nessun valore è presente, viene lanciata NoSuchElementException.
<X extends Throwable> T orElseThrow(Supplier<? extends X> funzEcc) throws X Restituisce il valore nell'oggetto invocante. Tuttavia, se nessun valore è presente, viene lanciata l'eccezione generata da funzEcc.
Stream<T> stream() Restituisce uno stream che contiene il valore dell'oggetto invocante. Se nessun valore è presente, lo stream non conterrà valori.
String toString() Restituisce una stringa corrispondente all'oggetto invocante.
Tabella 1: Metodi della classe Optional.

Il modo migliore per comprendere Optional è attraversare un esempio che utilizza i suoi metodi principali.

Alla base di Optional ci sono isPresent() e get(). È possibile determinare se un valore è presente chiamando isPresent(). Se un valore è disponibile, restituirà true. Altrimenti, viene restituito false. Se un valore è presente in un'istanza Optional, è possibile ottenerlo chiamando get(). Tuttavia, se si chiama get() su un oggetto che non contiene un valore, viene lanciata NoSuchElementException. Per questo motivo, si dovrebbe sempre prima confermare che un valore è presente prima di chiamare get() su un oggetto Optional. A partire da JDK 10, la versione senza parametri di orElseThrow() può essere utilizzata al posto di get(), e il suo nome aggiunge chiarezza all'operazione.

Naturalmente, dover chiamare due metodi per recuperare un valore aggiunge overhead a ogni accesso. Fortunatamente, Optional definisce metodi che combinano il controllo per un valore con il recupero del valore. Uno di questi metodi è orElse(). Se l'oggetto su cui viene chiamato contiene un valore, il valore viene restituito. Altrimenti, viene restituito un valore predefinito.

Optional non definisce alcun costruttore. Invece, utilizzeremo uno dei suoi metodi per creare un'istanza. Ad esempio, è possibile creare un'istanza Optional con un valore specificato utilizzando of(). È possibile creare un'istanza di Optional che non contiene un valore utilizzando empty().

Il seguente programma dimostra questi metodi:

// Dimostra diversi metodi Optional<T>
import java.util.*;

class DemoOptional {

    public static void main(String[] args) {
        // Creazione di un Optional di tipo String ma vuoto
        Optional<String> nessuno = Optional.empty();

        // Creazione di un Optional di tipo String con un valore
        // Utilizza Optional.of() per creare un Optional con un valore non nullo
        Optional<String> haValore = Optional.of("ABCDEFG");

        if (nessuno.isPresent()) {
            System.out.println("Questo non verrà visualizzato");
        } else {
            System.out.println("nessuno non ha valore");
        }

        if (haValore.isPresent()) {
            System.out.println("La stringa in haValore è: " +
                    haValore.get());
        } else {
            System.out.println("haValore non ha valore");
        }

        // Ottiene un valore predefinito se l'Optional è vuoto
        String strPred = nessuno.orElse("Stringa Predefinita");
        System.out.println(strPred);

    }

}

L'output è mostrato qui:

nessuno non ha valore
La stringa in haValore è: ABCDEFG
Stringa Predefinita

Come mostra l'output, un valore può essere ottenuto da un oggetto Optional solo se ne è presente uno. Questo meccanismo di base consente a Optional di prevenire le eccezioni di puntatore nullo.

Le classi OptionalDouble, OptionalInt e OptionalLong funzionano molto come Optional, tranne che sono progettate espressamente per l'uso su valori double, int e long, rispettivamente. Come tali, specificano i metodi getAsDouble(), getAsInt() e getAsLong(), rispettivamente, piuttosto che get(). Inoltre, non supportano i metodi filter(), ofNullable(), map(), flatMap() e or().