Optionals en Java - Parte 1. Entendiendo su uso.

A partir de la versión de Java 8 podemos utilizar Optional<T> para evitar las indeseables NullPointerExceptions.

Los objetos de tipo Optional almacenan un campo que puede tener valor o no ('null'). Ofrecen una serie de métodos para comprobar si hay valor almacenado, poder recuperarlo o devolver un valor por defecto en caso de que el valor almacenado sea 'null'.

De primeras parece una gran utilidad, pero no todo es oro lo que reluce :)
¿Cuando hay que utilizar este tipo de objeto? ¿Ya no volveré a utilizar 'null' nunca más? ¿De verdad es útil utilizar Optional?
En este artículo intentaré explicar el uso para el que está pensado Optional y las buenas prácticas para utilizarlo.

Veremos que sí que compensa utilizar Optional en nuestro código pero siguiendo una serie de normas y teniendo en cuenta cual es su verdadero propósito.

Vamos al lío.

Cuando utilizar Optional:

Optional está pensado únicamente para utilizarlo como valor de retorno de nuestros métodos.
Sirve para indicar al programador que un método en cuestión puede devolver valor 'null' y al mismo tiempo se le ofrece un mecanismo para que pueda recuperar de forma segura dicho valor y utilizarlo sin que se produzca 'NullPointerException'*:

Entonces, teniendo en cuenta lo anterior, NO se deberá utilizar Optional en los siguientes casos:

  1. Como tipo de dato para un campo de nuestras clases:

  1. En parámetros de un constructor:

  1. En parámetros de una método:


Ahora tenemos claro que solo debemos utilizar Optional como valor de retorno de nuestros métodos que puedan devolver 'null' en algún momento.

A continuación voy a mostrar un proyecto de Test para poder ver distintos casos en los que se utiliza Optional, como se le puede sacar más partido y unas cuantas normas a seguir para evitar que el propio uso de Optional nos provoque algún que otro problemilla.

Puedes descargar el código fuente desde GitHub.



Preparándonos para picar código:

Vamos a crear nuestras clases que servirán como modelo de datos para los tests.
En este caso tendremos una clase 'Person' que tendrá un campo del tipo 'Address' que a su vez también tendrá un campo del tipo 'State'.

Además tendremos nuestro pequeño repositorio llamado 'PersonRepository' para proveernos de distintas construcciones de 'Person' que utilizaremos para los tests.

Por último tenemos nuestra clase de tests 'OptionalUnitTest' donde iremos explicando uno por uno cada uno de los ejemplos y llegando a las conclusiones necesarias sobre el uso de 'Optional'.

Person.java

Address.java

State.java

PersonRepository.java

OptionalUnitTest.java


Creando Optionals:

Vamos a echarle un ojo a los tests correspondientes a la creación de Optionals para ver en detalle los distintos casos que podemos encontrarnos:

Podemos observar que si usamos 'Optional.of(valor)' debemos de asegurarnos primero que 'valor' no valga 'null', porque en ese caso se lanza una 'NullPointerException' (justamente lo que se pretende evitar utilizando Optionals).

Pero si en su lugar utilizamos 'Optional.ofNullable(valor)' no tendremos nunca problemas, ya que en caso de que 'valor' valga 'null' se creará un 'Optional' vacío y no se lanza ninguna Exception.

Por lo tanto aquí tenemos una norma importante que debemos seguir a la hora de crear nuestros objetos de tipo 'Optional':

A la hora de crear un objeto de tipo 'Optional' utilizar siempre 'Optional.ofNullable(valor)' en lugar de 'Optional.of(valor)'.



Recuperando el valor de un Optional:

Veamos ahora las distintas formas de recuperar el valor que contiene un 'Optional':

Primero vemos que para comprobar si un 'Optional' tiene valor o no podemos utilizar el método 'isEmpty()'.

Cuando queremos recuperar el valor que contiene el 'Optional', si usamos el método 'get()' y el 'Optional' está vacío (su contenido es 'null') se produce una 'NoSuchElementException'.
Si por el contrario nuestro 'Optional' tiene valor entonces con 'get()' lo recuperamos.
En este caso habría que utilizar antes el método 'isEmpty()' para saber si podemos utilizar 'get()' y que no nos lance la Exception.

Por último observamos como utilizando el método 'orElse()' podemos recuperar el valor de nuestro 'Optional' y en caso de que esté vacío podemos indicar como parámetro el valor que queremos que se devuelva evitando que se lance la 'NoSuchElementException' del método 'get()'.

Cuidado con el método 'orElse()':

Hemos visto que a la hora de recuperar el valor del 'Optional' es mejor utilizar siempre el método 'orElse()' en vez de 'get()' para evitar que se produzca la dichosa 'NoSuchElementException'.

Pero hay que tener cuidado porque el método 'orElse()' se ejecutará siempre, incluso cuando el 'Optional' tenga valor para devolver y en teoría no haría falta que se ejecutase el 'orElse()' porque no vamos a utilizar su valor.
Lo comprobamos en el siguiente test:

Esto puede no ser un problema cuando en el 'orElse()' estamos indicando como parámetro directamente un valor como por ejemplo 'orElse("DefaultValue")' o 'orElse(null)'.

Pero en los casos en los que en el 'orElse()' se esté ejecutando otro método para obtener el valor a devolver, sí que podemos tener problemas.
En casos como 'orElse(getUserFromDB())' o 'orElse(getUserHttp())', si el método utilizado como parámetro hace una consulta a la B.D. o una petición a un servicio web hay que tener en cuenta que siempre se va ejecutar, incluso en el caso de que el 'Optional' tenga valor para devolver y el valor devuelto por 'orElse()' no se vaya a utilizar nunca.

Recuperando el valor de un 'Optional' de forma segura y eficiente:

Llegados a este punto podemos pensar que esto de los 'Optionals' es un poco mierda, parece que dan más problemas que otra cosa, pero tranquilo, coge aire y recuerda que Java es para machotes :)

Llega al rescate el método 'orElseGet()':

El método 'orElseGet()' recibe como parámetro una lambda del tipo Supplier para indicar el valor que queremos devolver en caso de que el 'Optional' esté vacío (el valor que contiene es 'null'), pero en este caso, la función lambda que se envía en 'orElseGet()' solo se ejecuta si de verdad hace falta devolver dicho valor.
Es decir, con 'orElseGet()', si el 'Optional' tiene valor para devolver, nunca se ejecuta la función lambda.

Entonces ahora ya sí que podemos devolver desde nuestra función lambda un valor directamente, una consulta a la B.D. o una petición Http estando seguros de que solo se ejecutarán si es necesario.

Otra vez, podemos deducir otra norma a seguir, esta vez en el caso de que queramos recupar el valor de nuestro 'Optional':

A la hora de recuperar el valor de un objeto 'Optional' utilizar siempre el método 'orElseGet()' en lugar de 'get()' o 'orElse()'.



Conclusión:

En este artículo hemos visto cual es el propósito del uso de 'Optional' y como debe usarse.

También hemos deducido unas cuantas normas a seguir para que su uso sea una ventaja en vez de complicarnos más las cosas.

A modo de resumen:

1: Utilizar 'Optional' únicamente como valor de retorno de nuestros métodos que puedan devolver 'null' en algún momento.

2: No utilizar nunca 'Optional' en los siguientes casos:
-Como tipo de dato para un campo de nuestras clases.
-En parámetros de un constructor.
-En parámetros de una método.

3: A la hora de crear un objeto de tipo 'Optional' utilizar siempre 'Optional.ofNullable(valor)' en lugar de 'Optional.of(valor)'.

4: A la hora de recuperar el valor de un objeto 'Optional' utilizar siempre el método 'orElseGet()' en lugar de 'get()' o 'orElse()'.

En el próximo artículo sobre 'Optionals' veremos como sacarle partido a su uso y las ventajas prácticas que ofrece.

Espero que te haya servido de ayuda y que a partir de ahora empieces a utilizar 'Optional' en tu código.
Nos vemos.

Enlaces de interés: