Esta es la 3ª parte de artículos sobre 'Optionals' en Java.
En las entradas anteriores Optionals en Java - Parte 1. Entendiendo su uso y Optionals en Java - Parte 2. Evitando validaciones 'null' de 'if' anidados se explica como se deben utilizar los Optionals (y como no) y una de sus mayores ventajas para evitarnos las comprobaciones de 'null' con 'if' anidados de toda la vida.

Este artículo es continuación del anterior y trata de explicar cuando hay que utilizar 'map()' y cuando 'flatMap()' con nuestros 'Optionals'.

Optional.map():

Con el método 'map()' debemos de envíar como parámetro una lambda de tipo 'Function' que a su vez devuelve un tipo de dato en concreto. Posteriormente y de forma automática, el método 'map()' envuelve nuestro tipo de dato devuelto dentro de un 'Optional' para poder seguir operando con él.

En el ejemplo anterior, para 'map()' estamos utilizando una lambda que devuelve el campo 'Person.email' que es de tipo 'String', por lo tanto, estamos devolviendo directamente un 'String'.
Pero después de ejecutar nuestra función lambda, 'map()' automáticamente crea un 'Optional<String>' envolviendo nuestro 'email'. Éste 'Optional' devuelto es el valor que se sigue utilizando el flujo.

Por lo tanto, la línea '.orElse(null);' lo que está recibiendo es un 'Optional<String>'.

Optional.flatMap():

El método 'flatMap()' también recibe como parámetro una función lambda de tipo 'Function', pero en este caso, desde nuestra lambda debemos devolver un 'Optional' directamente.
Es decir, a diferencia de como funciona 'map()', con 'flatMap()' el resultado devuelto no se envuelve automáticamente en un 'Optional'.
Debemos devolver nosotros un objeto de tipo 'Optional'.

Caso práctico utilizando 'map()' y 'flatMap()':

Para ver cuando hay que utilizar un método u otro en la práctica, vamos a utilizar los modelos de datos del artículo anterior pero añadiendo unos cuantos métodos getter.
Seguiremos utilizando el mismo repositorio.

Person.java

Address.java

State.java

PersonRepository.java

Fíjate que en las clases 'Person', 'Address' y 'State' hemos añadido unos métodos getter que devuelven un objeto de tipo 'Optional' para cada campo.
Seguimos conservando los getters normales que devuelven el campo directamente.

Vamos ahora con unos cuantos tests para ver cuando hay que usar 'map()' y 'flatMap()':

map():

En este test utilizamos 'map()' para para obtener el campo 'Person.address.number'.
Fíjate que en las expresiones lambda de cada 'map()' se está llamando a los métodos getter que devuelven directamente el valor del campo correspondiente.
No se está llamando a los getter que devuelven el 'Optional' de cada campo.
Ese es el motivo de utilizar 'map()' en vez de 'flatMap()'.

flatMap():

En el test anterior se utiliza 'flatMap()' porque en cada expresion lambda estamos llamando al método getter que devuelve un 'Optional' con el valor de cada campo. Así, en este caso estamos devolviendo en cada 'flatMap()' un 'Optional' para seguir utilizándolo en el flujo.

Si en este caso, en vez de 'flatMap()' utilizásemos 'map()', lo que se estaría devolviendo sería un 'Optional<Optional<String>>' porque 'map()' envuelve automáticamente el resultado devuelto en un 'Optional', y no nos valdría.

map() y flatMap():

En este último test mostramos una combinación del uso de 'map()' y 'flatMap()'.

Si en nuestra expresión lambda utilizamos un getter que devuelve directamente el campo, entonces utilizamos 'map()' (porque ya se encarga de envolver el resultado automáticamente en un 'Optional').

Si en nuestra expresión lambda utilizamos un getter que devuelve un 'Optional' con el valor del campo, entonces utilizamos 'flatMap()' (porque ya estamos devolviendo un 'Optional').



Conclusión:

En este artículo hemos visto en que casos hay que utilizar los método 'map()' y 'flatMap()' con nuestros 'Optional' y la diferencia entre ellos.

Espero que te haya servido de utilidad.
Nos vemos.



Enlaces de interés:

-Código del artículo en GitHub
-Understanding, Accepting and Leveraging Optional in Java