La clase Optional
fue introducida como parte de Java, y a pesar que para muchos programadores es una forma de remplazar el uso de null
y manejar diferente los chequeo de null
para las variables, su propósito real es ayudar a introducir la programación funcional en Java. La meta de este artículo es proporcionar algunas buenas prácticas en el uso del Optional
en Java.
Creando un Optional en Java
Tenemos 2 métodos para crear una instancia de Optional:
Optional.of(value)
Optional.ofNullable(value)
Optional<String> myString1 = Optional.of("Hello world!"); //populated optional Optional<String> myString2 = Optional.of(null); //Throws an NullPointer Exception Optional<String> myString3 = Optional.ofNullable("Hello world!"); //populated optional Optional<String> myString4 = Optional.ofNullable(null); //empty optional
Vamos a utilizar Optional.ofNullable
siempre que sea posible por que el va a manejar correctamente cualquier valor null
. Como podemos ver en el código anterior, usando Optional.of(null)
va a lanzar un NullPointerException
, por lo tanto es mejor usarlo solamente cuando estamos 100% seguros que el valor nunca será null
.
isPresent() y isEmpty()
Podemos usar los métodos isPresent
y isEmpty
de la clase Optional
para saber si dicha instancia tiene un valor o nó. Pero, eso es exactamente lo que queremos evitar!
Optional<String> myOptional = doSomething(); String myValue; if (myOptional.isPresent()) { myValue = myOptional.get(); } else { myValue = null; }
No hay mayor diferencia entre usar el chequeo de null
(var != null)
a utilizar los métoos isPresent
o isEmpty
. No estamos sacando ventaje de los beneficios del Optional
. De hecho, nuestro código luce incluso más grande. Si reealmente queremos mejorar nuestro código debemos de cambiar nuestra forma de pensar y acercarnos a la programación funcional.
Optional y el estilo de programación funcional
Vamos a ver como la mayor parte de nuestro código usando Optional
puede ser mejorado usando métodos como: map
, filter
, getOrElse
, getOrElseThrow
, ifPresentOrElse
, reducinedo el uso de los métodos isEmpty
y isPresent
al minimo, y al mismo tiempo, haciendo nuestro código más sencillo.
Por ejemplo, en lugar de utilizar la declaración if(optional.isPresent)
, podemos reemplazarlo con el método ifPresentOrElse
or con ifPresent
, para ejecutar la lógica.
String myValue = null; Optional<String> myOptional = doSomething(); myOptional.ifPresent(foo -> { doMoreThings(foo);/** more code here **/});
Car myCar; Optional<Car> myOptionalCar = doSomethingCar(); myOptional.ifPresentOrElse( foo -> { /** more code here **/ }, () -> { /** more code here **/ } );
Algunas veces, no necesitamos ejecutar una lógica pero si obtener el valor de una manera segura y almacenarla en alguna variable. En esos casos, podemos usar el método orElse
(…)
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElse(new Foo());
O, hacerlo de una manera “lazy” donde podemos colocar código para crear el resultado del else
usando el método orElseGet
(…).
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElseGet(() -> {/**Some logic that returns a Foo**/});
Y que tal si necesitamos lanzar una Excepción cuando el Optional
es vacio, por ejemplo, buscando un registro en la base de datos. Para esos casos tenemos el método orElseThrow(()-> {...})
.
Optional<User> maybeUser = findUserById(userId); User user = maybeUser.orElseThrow(() -> new NotFoundException("User not found")); //If user is present we continue here.
Otros métodos importantes de la clase Optional
que podemos usar son filter
y map
. El map
es usado para transformar el resultado en otro objeto, una ventaja es que el map
no será llamado si el Optional
está vacio.
Optional<User> maybeUser = findUserById(userId); String userName = maybeUser.map(u -> user.getName()+ " " + user.getLastname()) .getOrElseThrow(() -> new NotFoundException("User not found"));
Podemos usar también el método filter
para reducir el resultado. Si este resultado no comple con la condición del filter
retornará un Optional
vació.
Optional<User> maybeUser = findUserById(userId); User adultUser = maybeUser.filter(u -> u.getAge() > 18 ) .getOrElseThrow(() -> new NotFoundException("User is not an adult"));
Finalmente, podemos notar como es posible usar más de un método del Optional
en la misma linea, por ejemplo: map -> filter -> map-> getOrElse. Por lo tanto nuestro código requiere menos líneas.
Cuando y cuando no usar el Optional
Como hemos visto hasta el momento la clase Optional
is excelente para ser usada como un tipo de retorno (ese es su proposito original), pero que podemos decir hacer de usarla en campos/atributos de una clase, o parámetros de un método. Estas son las mejores prácticas:
– Atributos/Campos
En resumen, evitar usar Optional
en atributos/campos de una instancia. Porqué?
- Porque la clase
Optional
no esSerializable
. - Porque los campos constituyen parte del estado del objeto.Como una alternativa, podemos create un método accesor (getter) que returne un
Optional
.
public class MyObject { private String myField; public Optional<String> getMyField(){ return Optional.ofNullable(myField); } }
– Parámetros
No hay ninguna restricción en usar un Optional
como un parámetro, pero tengamos en mente que ese Optional
sigue siendo un Object
y cualquier programador podría mandarlo como un null
en la llamada de un método.
References
- https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html
- https://dzone.com/articles/optional-in-java
- https://www.arquitecturajava.com/optional-map-y-el-concepto-de-delegacion/
- https://www.baeldung.com/java-optional