¿Posiblemente has trabajado con alguna librería que te devuelve un Optional <T> y te puedes preguntar para qué complicar esto y no retorna sólo el valor? ¿O en qué me beneficia esto?
Bueno la verdad es que trae bastantes ventajas si lo sabemos aprovechar, cómo usarlo, cuándo si y cuando no. Espero pueda transmitirte mis conocimientos de Optional al terminar este artículo.
La clase Optional llegó a Java a partir de la versión 8, en conjunto a las lambdas y a los Streams que van bastante de la mano, si no te manejas mucho con ellos, podrías ver este post . Por esto para usarlo simplemente necesitas usar la versión 8 e importando el siguiente package:
Más que nada es evitar los queridos NullPointerException, para esto te hace evaluar ¿Qué pasaría si lo que estoy llamando viene nulo? Por ejemplo:
Digamos que este findById no consigue un registro con este id, en este caso debería retornar un nulo, cosa que si no estamos atentos nos traería la exception que comente anteriormente, como en este ejemplo:
Entonces para este caso tenemos la ventaja de los optionals, cambiando el código a la siguiente manera:
Para luego al ser llamado:
Si es un poco mas verboso, cierto, aunque luego te explico como hacerlo en menos líneas, se cumple la función de hacer que el programador que utilice la función este consciente de que puede que venga o no un valor.
Para mejor entendimiento de los Optional utilizaremos como ejemplo el calcular un promedio de un arreglo de números.
Digamos que el arreglo venga vacío, en ese caso no tiene sentido colocar el promedio como 0,porque confundiría en el caso de que realmente el promedio sea 0, para esto el optional tiene la opción de crear un optional vacío, el cual permitiría distinguir entre 0 y sin valor, para esto se utiliza el siguiente método:
Por otro lado, si calculamos el promedio y estamos seguros de que tiene un valor podemos crear un optional del valor, en este caso va a ser un optional de un Double, aunque podría ser de cualquier clase, (Object, String, MyCustomClass...) y para crearlo utilizamos esta función:
Llevando a la práctica todo lo de arriba, en nuestro ejemplo podríamos ver que en caso de que le pasen como parámetro un arreglo vacío, el optional estará vacío, y en caso contrario mandará el valor correspondiente, quedando de la siguiente manera:
Existe último método para crear un optional, el cual es mas que nada para evitarnos el tener que hacer algo parecido a esto:
Donde simplemente escribimos:
Que funciona exactamente igual que en el ejemplo anterior… Y si en el caso de que estés pensando ¿Qué pasaría si escribo Optional.of(null)? Bueno lanzaría unNullPointerException que es lo primero que estamos evitando… La ironía de la vida ☺.
En el caso de que llamemos un método que nos retorne un optional, o que simplemente hayamos creado nosotros mismo un optional, tiene varios métodos que nos pueden ayudar a manejar nuestro valor evitando cualquier NullPointerException.
Una de las ventajas que trae el optional es que funciona con programación funcional, cosa que nos ahorra bastante código, aunque en el ejemplo de ver si el valor existía. O nomás bien era más verboso, esto es porque no se le está sacando todo el provecho a la clase, por lo que este ejemplo:
Se podría reducir a simplemente esto:
¿Bastante más corto no? Incluso si usas java 10 o una versión posterior, podrías dejarlo como:
el cual lanzaría un NoSuchElementException.
En el caso de que no quisiéramos lanzar ninguna exception, podríamos generar un valor por defecto en este caso al usar “orElse” u “orElseGet” podríamos hacerlo, la diferencia entre estos es que el orElse tiene como parámetro el valor, y el orElseGet tiene un Supplier como parámetro:
que incluso podría cambiarse por esto:
En estos dos casos, podríamos decir que: ‘si no existe un valor entrégame 0’ o también generar un valor random.
Hay que tomar en cuenta que con estos tres métodos orElse, orElseGet, orElseThrow si el valor existe no pasaría lo que se genera en esta parte del código, ejemplo:
Este caso retorna true en el caso de que tenga un valor
A partir de java 11existe este método, y es prácticamente un !isPresent();
Este es bastante similar al isPresent (ve la diferencia entre la f y la s) pero esta toma como parámetro un Consumer en caso de que el valor existe (puedes verlo como un if sin un else)
En caso de que quisieras seguir trabajando con un optional, en caso de que no exista el valor y quisieras darle un valor específico, este método existe a partir de java 9:
Este método también viene a partir de java 9, en este caso tiene 2 parámetros y es prácticamente ver un if else …
Los optional actúan de manera similar con los métodos de Stream , map, flatMap, filter.
Aunque en el caso de flatMap es por si tienes un Optional<Optional<T>> para convertirlo en un Optional<T>
Ejemplos:
Este método existe a partir de java 9, con lo que anteriormente hubiésemos tenido que hacer algo así :
Como comenté en un principio el objetivo de los Optional es darle entender a los programadores que la función que esta llamando, puede o no traer un valor, y hacernos pensar que hacer en el caso de que no tengamos un valor, dado esto mas que nada el valor ideal para los optional es solo para el return type.
Pero toma en cuenta que para los parámetros no es como si fuera un valor opcional, además, de que hay que tomar en cuenta que los Optional consumen más recursos que el valor por si solo, por esto es mejor evitarlo cuando no es necesario…
En el caso de que tengas una variable digamos un String no vale la pena generar un Optional solo para ver si el valor es distinto de nulo, tomando en cuenta que consume más recursos por que el optional es un wrapper del valor, por lo que es como decir que hay un Object dentro de otro, cuando solo validando el null utilizamos un solo valor, además de que puede que a veces sea más difícil de leer:
Aunque digamos que tenemos un objeto con varios objetos anidados, en este caso podría ayudarnos a la lectura y además con los tests unitarios (esto porque evitamos múltiples paths de los tests en los if)
Así nos evitamos un:
Toma en cuenta que se hicieron varios maps, porque si al tomar “subObjectA” y es nulo al intentar tomar “subObjectB” lanzaría un nullPointer, como en este caso:
Este caso ayuda con los tests unitarios dado que los paths para el coverage se reducen de muchos(si ninguno es nulo, si el 3ro es nulo, si el segundo es nulo, si el primero es nulo…) a solo dos, sólo si existe o no el valor
En el caso de que escribas una clase que tenga valores opcionales, es mejor que los dejes en nulo, y que retornes en los gets un Optional del valor en este caso tenemos una clase persona, que tiene como atributos el nombre, apellido y sobrenombre los dos primeros son necesarios y el sobrenombre es opcional:
Este es un buen ejemplo de como implementarlo dado que da a entender a los programadores que utilicen esta clase que el nickName es opcional y allá ellos que hacen si no viene el valor.
Por otro lado, digamos este ejemplo:
cómo ves el ejemplo es prácticamente igual, aunque en este caso el nickName es un Optional aquí hay 2 cosas que están mal.
Primero como comenté anteriormente es más pesado usar Optional en vez de simplemente el valor, por esto declarar el campo (field, Instance variable o como prefieras llamarlo) lo haría mas pesado, digamos que creamos muchas instancias de esta clase, al final va a pesar casi 4 veces más por solo agregar este en vez de dejarlo solo como String.
En el caso del constructor, si quisieras que un valor sea opcional es mejor sobrecargar el constructor (también aplica para cualquier método), así en el caso de que la clase sea muy grande te evitas tener que escribir código innecesario como este:
O incluso algo peor que nos llevaría a la siguiente regla
En el caso anterior si no viste por qué “la gente sólo quiere ver el mundo arder” digamos que llamas al getNickName:
Que un Optional te tire un NullPointerException, un disparo duele menos, darse cuenta de ese error no están simple tampoco porque “los Optional son para evitar los NullPointer y ya agregué un orElse para validar” así que puede que sea la última opción que revises para comprobar el error; así que si necesitas un Optional vacío, simplemente usa Optional.Empty()
Para terminar, recuerda que el Optional es para hacerle entender a los otros programadores, que el valor que esperan venga o no, siempre tomando en cuenta que un Optional nunca debería ser nulo (no hagas que el mundo arda, sólo tenemos uno ☺)