Java opcional como tipo de retorno

1. Introducción

El tipo Optional se introdujo en Java 8. Proporciona una forma clara y explícita de transmitir el mensaje de que puede que no haya un valor, sin utilizar null .

Al obtener un tipo de retorno opcional , es probable que verifiquemos si falta el valor, lo que genera menos NullPointerException en las aplicaciones. Sin embargo, el tipo Opcional no es adecuado en todos los lugares.

Aunque podemos usarlo donde lo consideremos adecuado, en este tutorial, nos centraremos en algunas de las mejores prácticas para usar Opcional como tipo de retorno.

2. Opcional como tipo de devolución

Un tipo opcional puede ser un tipo de retorno para la mayoría de los métodos, excepto algunos escenarios que se describen más adelante en el tutorial.

La mayoría de las veces, devolver un Opcional está bien:

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

Esto es útil ya que podemos usar la API opcional en el método de llamada:

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

También es apropiado que un método estático o un método de utilidad devuelva un valor opcional . Sin embargo, hay muchas situaciones en las que no deberíamos devolver un tipo Opcional .

3. Cuándo no devolver Opcional

Dado que Optional es una clase contenedora y basada en valores, hay algunas operaciones que no se pueden realizar con el objeto Optional . Muchas veces, es simplemente mejor devolver el tipo real en lugar de un tipo opcional .

En términos generales, para los captadores en POJO, es más adecuado devolver el tipo real, no un tipo opcional . En particular, es importante que los Entity Beans, los modelos de datos y los DTO tengan captadores tradicionales.

Examinaremos algunos de los casos de uso importantes a continuación.

3.1. Publicación por entregas

Imaginemos que tenemos una entidad simple:

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

En realidad, esto no funcionará en absoluto. Si intentáramos serializar esto, obtendríamos una NotSerializableException :

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

Y realmente, aunque serializar Opcional puede funcionar con otras bibliotecas, ciertamente agrega lo que puede ser una complejidad innecesaria.

Echemos un vistazo a otra aplicación de este mismo desajuste de serialización, esta vez con JSON.

3.2. JSON

Las aplicaciones modernas convierten objetos Java a JSON todo el tiempo. Si un captador devuelve un tipo opcional , lo más probable es que veamos alguna estructura de datos inesperada en el JSON final.

Digamos que tenemos un bean con una propiedad opcional:

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

Entonces, si usamos Jackson para serializar una instancia de Optional , obtendremos:

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

Pero, lo que realmente queremos es:

{"firstName":"Baeldung"}

Entonces, Optional es un problema para los casos de uso de serialización. A continuación, veamos el primo de la serialización: escribir datos en una base de datos.

3.3. JPA

En JPA, el captador, el definidor y el campo deben tener un nombre y un acuerdo de tipo. Por ejemplo, un campo firstName de tipo String debe emparejarse con un getter llamado getFirstName que también devuelve String.

Seguir esta convención simplifica varias cosas, incluido el uso de la reflexión por parte de bibliotecas como Hibernate, para brindarnos un gran soporte de mapeo Object-Relational.

Echemos un vistazo a nuestro mismo caso de uso de un nombre opcional en un POJO.

Esta vez, sin embargo, será una entidad JPA:

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

Y sigamos adelante e intentemos persistir:

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

Lamentablemente, nos encontramos con un error:

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)]

Podríamos intentar desviarnos de este estándar. Por ejemplo, podríamos mantener la propiedad como String , pero cambiar el getter:

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

Parece que podríamos tener ambas formas: tener un tipo de retorno opcional para el captador y un campo persistente firstName .

Sin embargo, ahora que somos inconsistentes con nuestro captador, definidor y campo, será más difícil aprovechar los valores predeterminados de JPA y las herramientas de código fuente IDE.

Hasta que JPA tenga un soporte elegante de tipo opcional , debemos ceñirnos al código tradicional. Es más simple y mejor:

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

Por último, echemos un vistazo a cómo afecta esto a la interfaz: compruebe si el problema que encontramos le suena familiar.

3.4. Lenguajes de expresión

La preparación de un DTO para el front-end presenta dificultades similares.

Por ejemplo, imaginemos que estamos usando plantillas JSP para leer el firstName de nuestro UserOptional DTO de la solicitud:

Como es opcional , no veremos " Baeldung ". En su lugar, veremos la representación de cadena del tipo opcional :

Optional[Baeldung] 

Y esto no es un problema solo con JSP. Cualquier lenguaje de plantillas, ya sea Velocity, Freemarker o cualquier otro, deberá agregar soporte para esto. Hasta entonces, sigamos manteniendo nuestros DTO simples.

4. Conclusión

En este tutorial, hemos aprendido cómo podemos devolver un objeto opcional y cómo lidiar con este tipo de valor de retorno.

Por otro lado, también hemos aprendido que hay muchos escenarios en los que sería mejor no usar el tipo de retorno opcional para un getter. Si bien podemos usar el tipo Opcional como una pista de que podría no haber un valor no nulo, debemos tener cuidado de no abusar del tipo de retorno Opcional , particularmente en un captador de un bean de entidad o un DTO.

El código fuente de los ejemplos de este tutorial se puede encontrar en GitHub.