Java opzionale come tipo di restituzione

1. Introduzione

Il tipo Optional è stato introdotto in Java 8. Fornisce un modo chiaro ed esplicito per trasmettere il messaggio che potrebbe non esserci un valore, senza utilizzare null .

Quando si ottiene un tipo restituito opzionale , è probabile che controlliamo se il valore è mancante, portando a meno NullPointerException nelle applicazioni. Tuttavia, il tipo Opzionale non è adatto in tutti i luoghi.

Sebbene possiamo usarlo ovunque lo ritenga opportuno, in questo tutorial ci concentreremo su alcune best practice per l'utilizzo di Optional come tipo di ritorno.

2. Facoltativo come tipo di restituzione

Un tipo facoltativo può essere un tipo restituito per la maggior parte dei metodi, ad eccezione di alcuni scenari discussi più avanti nell'esercitazione.

La maggior parte delle volte, restituire un Opzionale va bene:

public static Optional findUserByName(String name) { User user = usersByName.get(name); Optional opt = Optional.ofNullable(user); return opt; }

Questo è utile poiché possiamo utilizzare l' API opzionale nel metodo chiamante:

public static void changeUserName(String oldFirstName, String newFirstName) { findUserByFirstName(oldFirstName).ifPresent(user -> user.setFirstName(newFirstName)); }

È anche appropriato che un metodo statico o un metodo di utilità restituisca un valore facoltativo . Tuttavia, esistono molte situazioni in cui non dovremmo restituire un tipo facoltativo .

3. Quando non restituire Facoltativo

Poiché Facoltativo è un wrapper e una classe basata sul valore, alcune operazioni non possono essere eseguite sull'oggetto Facoltativo . Molte volte, è semplicemente meglio restituire il tipo effettivo piuttosto che un tipo opzionale .

In generale, per i getter nei POJO, è più adatto restituire il tipo effettivo, non un tipo opzionale . In particolare, è importante che i bean di entità, i modelli di dati e i DTO abbiano getter tradizionali.

Esamineremo alcuni dei casi d'uso importanti di seguito.

3.1. Serializzazione

Immaginiamo di avere un'entità semplice:

public class Sock implements Serializable { Integer size; Optional pair; // ... getters and setters }

Questo in realtà non funzionerà affatto. Se dovessimo provare a serializzare questo, avremmo un'eccezione NotSerializableException :

new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(new Sock()); 

E in realtà, sebbene la serializzazione di Optional possa funzionare con altre librerie, aggiunge sicuramente quella che potrebbe essere una complessità non necessaria.

Diamo un'occhiata a un'altra applicazione della stessa mancata corrispondenza di serializzazione, questa volta con JSON.

3.2. JSON

Le applicazioni moderne convertono sempre gli oggetti Java in JSON. Se un getter restituisce un tipo facoltativo , molto probabilmente vedremo una struttura dati inaspettata nel JSON finale.

Supponiamo di avere un bean con una proprietà opzionale:

private String firstName; public Optional getFirstName() { return Optional.ofNullable(firstName); } public void setFirstName(String firstName) { this.firstName = firstName; }

Quindi, se usiamo Jackson per serializzare un'istanza di Optional , otterremo:

{"firstName":{"present":true}} 

Ma quello che vorremmo davvero è:

{"firstName":"Baeldung"}

Quindi, Opzionale è un problema per i casi d'uso della serializzazione. Successivamente, diamo un'occhiata al cugino della serializzazione: scrivere dati su un database.

3.3. JPA

In JPA, il getter, il setter e il campo devono avere un nome e un tipo di accordo. Ad esempio, un campo firstName di tipo String dovrebbe essere accoppiato con un getter chiamato getFirstName che restituisce anche un String.

Seguire questa convenzione rende molte cose più semplici, incluso l'uso della riflessione da parte di librerie come Hibernate, per darci un ottimo supporto per la mappatura relazionale-oggetto.

Diamo un'occhiata al nostro stesso caso d'uso di un nome opzionale in un POJO.

Questa volta, però, sarà un'entità JPA:

@Entity public class UserOptionalField implements Serializable { @Id private long userId; private Optional firstName; // ... getters and setters }

E andiamo avanti e proviamo a persistere:

UserOptionalField user = new UserOptionalField(); user.setUserId(1l); user.setFirstName(Optional.of("Baeldung")); entityManager.persist(user);

Purtroppo, ci imbattiamo in un errore:

Caused by: javax.persistence.PersistenceException: [PersistenceUnit: com.baeldung.optionalReturnType] Unable to build Hibernate SessionFactory at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1015) at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:941) at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54) at com.baeldung.optionalReturnType.PersistOptionalTypeExample.(PersistOptionalTypeExample.java:11) Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: UserOptionalField, for columns: [org.hibernate.mapping.Column(firstName)]

Potremmo provare a deviare da questo standard. Ad esempio, potremmo mantenere la proprietà come String , ma modificare il getter:

@Column(nullable = true) private String firstName; public Optional getFirstName() { return Optional.ofNullable(firstName); }

Sembra che potremmo avere entrambi i modi: avere un tipo restituito opzionale per il getter e un campo persistente firstName .

Tuttavia, ora che non siamo coerenti con il nostro getter, setter e field, sarà più difficile sfruttare le impostazioni predefinite JPA e gli strumenti del codice sorgente IDE.

Fino a quando JPA non avrà un elegante supporto di tipo opzionale , dovremmo attenerci al codice tradizionale. È più semplice e migliore:

private String firstName; // ... traditional getter and setter

Diamo finalmente un'occhiata a come questo influisce sul front-end: controlla per vedere se il problema che incontriamo suona familiare.

3.4. Linguaggi di espressione

Preparare un DTO per il front-end presenta difficoltà simili.

Ad esempio, immaginiamo di utilizzare il template JSP per leggere il firstName del nostro UserOptional DTO dalla richiesta:

Dal momento che è un optional , non vedremo " Baeldung ". Invece, vedremo la rappresentazione String del tipo Opzionale :

Optional[Baeldung] 

E questo non è un problema solo con JSP. Qualsiasi linguaggio di modellazione, sia esso Velocity, Freemarker o qualcos'altro, dovrà aggiungere il supporto per questo. Fino ad allora, continuiamo a mantenere semplici i nostri DTO.

4. Conclusione

In questo tutorial, abbiamo imparato come restituire un oggetto opzionale e come gestire questo tipo di valore restituito.

D'altra parte, abbiamo anche appreso che ci sono molti scenari in cui sarebbe meglio non utilizzare il tipo di ritorno facoltativo per un getter. Sebbene possiamo usare il tipo opzionale come suggerimento che potrebbe non esserci alcun valore non nullo, dovremmo fare attenzione a non usare eccessivamente il tipo restituito opzionale , in particolare in un getter di un bean di entità o di un DTO.

Il codice sorgente degli esempi in questo tutorial può essere trovato su GitHub.