Java Optional y buenas prácticas
Desde que apareció Java 8 hace ya unos cuantos años, y la introducción con esta versión de los Optional, he visto multitud multitud de veces un mal uso de los Optional o un uso no del todo correcto, por lo que en esta entrada vamos a hablar un poco de Java Optional y buenas prácticas, ya que usar bien el Optional no es una opción ;).
Brian Goetz, hizo una buena definición de Java Optional, en una pregunta que se hizo:
Pregunta:
¿Es una buena práctica devolver el tipo Optional<Foo> en lugar de Foo?, teniendo en cuenta que el valor puede ser nulo.
Respuesta:
Of course, people will do what they want. But we did have a clear intention when adding this feature, and it was not to be a general purpose Maybe type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent «no result», and using null for such was overwhelmingly likely to cause errors.
For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.
I think routinely using it as a return value for getters would definitely be over-use.
There’s nothing wrong with Optional that it should be avoided, it’s just not what many people wish it were, and accordingly we were fairly concerned about the risk of zealous over-use.
Brian Goetz
Básicamente lo que nos viene a decir Brian Goetz en esta cita, es que no debería de hacerse un uso excesivo de Optional, y que no es malo evitar su uso, así como no se debería devolver en métodos get, o en listas, o envolver cualquier objeto.
A continuación vamos a ver unas cuántas «cosas» que hacemos con el Optional que deberíamos de evitar, o no usar Optional en esos casos, vamos a ello:
Buenas prácticas con Optional
No asignar null a un Optional
Uno de los usos excesivos que se hace de Optional es cuando se pretende utilizar de la manera tradicional asignando null a un optional:
Optional<User> user = null;
Si necesitas algo como lo de arriba es mejor asignarlo a vació, o no usarlo:
Optional<User> user = Optional.empty();
No usar Optional en Constructores
Hay que tener en cuenta que al final Optional es un envoltorio, por lo que si lo usamos dentro de un constructor, estamos añadiendo una complejidad excesiva e innecesaria:
Evitar hacer esto:
public class Car { private final String model; private final Optional<String> color; public Car(String model, Optional<String> color) { this.model = model; this.color = color; } }
Mejor hacerlo así:
public class Car { private final String model; private final String color; public Car(String model, String color) { this.model = model; this.color = color; } }
No usar Optional con Listas
Una práctica que no es muy recomendable es crear un optional de una lista, ya que si la lista es null, Optional también sería null y además su tratamiento es más complejo.
Evitar: Optional<List<String>> cars = Optional.of(List.of("ford","renault");
Utilizar: List<String> cars = List.of("ford","renault");
No usar Optional como parámetro en un método
Pasar un parámetro con optional en un método es contraproducente ya que habría que añadir lógica condicional para tratar este parámetro, mejor verlo con un ejemplo:
Evitar:
public void car(Optional<String> models) { if (models.isPresent()) { doSomething(models.get()); } else { doSomethingElse(); } }
Mejor opción sería:
public void car(String models) { if (null != models) { doSomething(models); } else { doSomethingElse(); } }
La anterior sería mejor opción, puedes ver que el código es bastante similar, pero computacionalmente hablando estas haciendo un envoltorio innecesario y es más costoso para el compilador.
Usar orElse() o orElseThrow() en lugar de isPresent() o get()
En lugar de tener que hacer varios pasos para obtener un valor, podemos mediante lambdas hacer una operación encadenada para obtener el valor.
Evitar
List<Car> cars = //lista de coches; Optional<Car> findCar = cars.stream() .filter(car -> c.getColor().equalsIgnoreCase("blue") .findFirst(); if (findCar.isPresent()) { return findCar.get().getModel(); } else { return throw new NotFoundExcepcion("Car blue not found"); }
Mejor opción sería:
List<Car> cars = //lista de coches; Optional<Car> findCar = cars.stream() .filter(car -> c.getColor().equalsIgnoreCase("blue") .findFirst() .map(Car::getModel) .orElseThrow(()-> new NotFoundException("Car blue not found")
Siempre que se pueda hay que evitar .isPresent() y hacerlo en cadena.
Evitar usar Optional como un operador ternario
Pensamos que Optional puede ser una solución para poder evitar todo tipo de null, pero si aplicamos optional para todo hacemos nuestro código mucho más verboso.
Evitar:
return Optional.ofNullable(value).orElse("BLUE");
Mejor así:
return value == null ? "BLUE" : value;
No usar Optional al devolver valor de un método
Hay que evitar devolver un método con un Optional.
Evitar:
public Optional<List<String>> getDoctors(String name) { Hospital hospital = new Hospital (List.of("Pepe", "Juan")) List<String> doctors = hospital.getDoctors(); return Optional.ofNullable(doctors); }
Mejor hacerlo así:
public List<String> getDoctors(String name) { Hospital hospital = new Hospital (List.of("Pepe", "Juan")) List<String> doctors = hospital.getDoctors(); return doctors!= null ? items: Collections.emptyList(); }
No es necesario usar optional para devolver una lista vacía o devolver un método como Optional, para evitar los null, se comprueba previamente.
No confundirse entre Optional.of() y Optional.ofNullable()
Es bastante común aplicar estos dos conceptos de manera equivocada, para ello tener en cuenta simplemente que Optional.of(null) dará un NullPointer y Optional.ofNullable(null) dara un Optional.empty.
Evitar:
public Optional<String> getModelCar() { String modelCar = null; return Optional.of(modelCar); }
En el caso en el que modelCar sea null se lanzará una excepción de null pointer.
Mejor hacerlo así:
public Optional<String> getModelCar() { String modelCar = null; return Optional.ofNullable(modelCar); }
Cuando se sepa seguro que se tiene valor entonces se usará Optional.of(«something»)
Consumir un Optional si esta presente si no acción vacía
Utilizar en el optional ifPresentOrElse es una muy buena alternativa al isPresent y get.
Evitar:
Optional<String> modelCar = ... ; if(modelCar.isPresent()) { System.out.println("Model is: " + modelCar.get()); } else { System.out.println("Model not found"); }
Mejor hacerlo así:
Optional<String> modelCar = ... ; modelCar.ifPresentOrElse( System.out::println, () -> System.out.println("Model not found") );
Devolver un null con orElse() en lugar de con un isPresent() and Else
Si alguna vez necesitamos que si un optional no tiene valor devolver un null, es mejor utilizar la opción orElse() en lugar de hacer un ifPresent() para a continuación hacer un else:
Evitar:
Optional<String> modelCar = ... ; if(modelCar.isPresent()) { return modelCar.get() } else { return null; }
Mejor hacerlo así
Optional<String> modelCar = ... ; return modelCar.orElse(null);
Evitar usar Optional <T> y mejor utilizar sus genéricos OptionalInt, OptionalLong, o OptionalDouble
Si tienes que usar un optional para un int o long o Double, es mejor usar OptionalInt, OptionalLong o OptionalDouble, ya que computacionalmente hablando son menos costosos, además de simplifica mucho el código, y es una mejor práctica de uso:
Evitar:
Optional<Long> value = Optional.of(1L); Optional<Double> value = Optional.of(1.5d); Optional<Integer> value = Optional.of(1);
Mejor hacerlo así:
OptionalDouble value = OptionalDouble.of(1.5d); OptionalLong value = OptionalLong.of(1L); OptionalInt value = OptionalInt.of(1);
Conclusión
En esta entrada hemos intentando analizar un poco las opciones que tenemos con Optional y buenas prácticas, para poder hacer un mejor uso de Optional.
Si quieres seguir con programación funcional en Java puedes echar un ojo a alguno de nuestros enlaces sobre java.