Enviado por juanonlab el Mié, 27/03/2019 - 17:40
Optional en Java

Muchas veces nos encontramos con alguna funcionalidad nueva en un lenguaje de programación y ya sea por tiempo o por falta de comprensión hacemos un uso no muy adecuado del mismo. Desde La version 1.8 surgió la posibilidad de manejar de una manera cómoda el clásico problema del null pointer exception con la aparición de la clase Optional.

A modo de resumen rápido diré que Optional es una clase que encapsula un objeto para poder hacer ciertas operaciones/comprobaciones sobre él.

Conocí su existencia con aplicaciones springboot que recuperan información a partir de un repositorio. Por ejemplo, en vez de obtener un objeto Customer de la base datos se recupera un objeto Optional<Customer>.

En este humilde blog no voy a hacer una descripción exhaustiva de esta clase. Hay un tutorial muy bueno de la propia Oracle donde se explica muy bien el uso de Optional con más detalle. En su lugar haré un resumen y mostraré con un ejemplo las ventajas que ofrece Optional. Dejo el enlace a la web de Oracle que habla sobre Optional.

A continuación mostraré una alternativa para evitar la tentación de recuperar del Optional nuestro objeto con get. Esa no es la idea del uso del Optional. Si no se le ve utilidad o no se va a utilizar correctamente mejor no utilizarlo porque se va a añadir una complejidad innecesaria a los proyectos.

De todas las posibilidades que ofrece Optional también hablaré de las que me han parecido más interesantes.

Me he creado un ejemplo similar al del tutorial de Oracle pero con un nivel de profundidad más en la jerarquía de objetos. Dejo un diagrama para que se comprenda mejor:

Jerarquía de objetos

Cada objeto está contenido en un Optional

 

Cuando existe una jerarquía de objetos como esta se hace tedioso acceder al último objeto mediante comprobaciones de null para cada objeto. Además, la posibilidad de generar errores es muy alta ya que se te puede pasar alguna comprobación. Para obtener información del último objeto podemos usar Optional y expresiones lambda para acceder de una manera elegante y con menos errores a los datos.

 

Ejemplo con optional

En mi ejemplo guardo información de 3 coches y los muestro por pantalla. Algunos coches no tienen toda la información y sus objetos están vacíos. Sin realizar ninguna comprobación de null se va a poder obtener la información cuando esta esté disponible. En algunos casos al no encontrar la información se devuelve un valor por defecto (por ejemplo el año 1980 y No name). A los coches se les aplica un filtro para saber si sus ruedas son de baja calidad. El primer modelo tiene ruedas de buena calidad ("A"), el segundo no dispone de esa información y el último tiene ruedas de baja calidad ("C").

He usado lombook para evitarme los getter y setter habituales de las clases POJO.

Puedes verificar el comportamiento completo del programa accediendo al repositorio en github.

Salida al ejecutar el programa:

Car --> Opel Corsa
 Tyre Name --> Dunlop Sport
 Tyre Year --> 2017
 
Car --> Opel Kadett
 Tyre Name --> No Name
 Tyre Year --> 1980
 
Car --> Lada Niva
 Tyre Name --> Basic Model
 Tyre Year --> 1998
 Car with low Quality tyres

Obtener la información del objeto que hay en Optional

theCar es un objeto Optional que contiene el objeto Car que contiene a su vez un string llamado model.

- Lo más sencillo para imprimir el modelo es hacer:

System.out.println(" \nCar --> " + theCar.map(Car::getModel).orElse(NO_NAME));

Con orElse podemos devolver el valor "No name" si el modelo es nulo.

- No recomendado:

 // NO!!
System.out.println(" Car --> " + (theCar.isPresent() ? theCar.get().getModel() : NO_NAME));

- Nada recomendado:

 // NO!!
 if (theCar.get() != null) {
   System.out.println(" Car --> " + theCar.get().getModel());
 } else {
   System.out.println(" Car --> NO_NAME");
 }

La idea es evitar el uso de get y servirte de las nuevas funcionalidades de Optional. Los métodos isPresent (también existe IfPresent) son métodos validos de Optional pero con map y orElse tenemos en el caso de la impresión una solución sencilla en una sola instrucción.

 

Inicializar un optional vacío

Optional.empty();

Es muy sencilla la inicialización de objetos Optional.

 

Crear un optional con contenido

Optional<Car> theCar = Optional.of(car);

Mediante Optional.of guardamos dentro de Optional el tipo de objeto que queramos.

 

Obtener la información en la jerarquía de objetos

 private static String getBrandTyreName(final Optional<Car> theCar) {
  return theCar.flatMap(Car::getTyre).flatMap(Tyre::getBrand).flatMap(BrandTyre::getBrandTyreInfo)
      .map(BrandTyreInfo::getName).orElse(NO_NAME);
 }

Con flatMap accedemos directamente al contenido del Optional y encadenamos varias llamadas para llegar al objeto que queremos mostrar por pantalla.

Sin el Optional se hace complicado acceder rápidamente a ciertos elementos

 private static String getBrandTyreYearOldSchool(final Optional<Car> theCar) {    
 if (theCar.get()!=null) {
     if (theCar.get().getTyre() != null) {
         if (theCar.get().getTyre().get().getBrand()!=null) {
             if (theCar.get().getTyre().get().getBrand().get().getBrandTyreInfo() !=null) {
                 String tyreName = theCar.get().getTyre().get().getBrand().get().getBrandTyreInfo().get().getName();
                 if (tyreName != null) {
                     return tyreName;
                 } else {
                     return NO_NAME;
                 }
             } else {
                 return NO_NAME;
             }
         }
         else {
             return NO_NAME;
         }
     } else {
         return NO_NAME;
     }
 } else {
     return NO_NAME;
 }                    
}

En este caso el uso de Optional  es una buena opción.

 

Uso de filtros

 private static void lowQualityTyres(final Optional<Car> theCar) {
   theCar.flatMap(Car::getTyre).filter(tyre -> "C".equals(tyre.getQuality()))
   .ifPresent(tyre -> System.out.println(" Car with low Quality tyres"));
 }

Podemos filtrar y sólo imprimir los vehículos con ruedas de categoría "C". Hay que observar que el vehículo Opel Kadett tiene el objeto Tyre inicializado con Optional.empty(). De esta manera nos evitamos obtener algún error del tipo null pointer.

 

El uso de Optional puede ser muy útil en muchas situaciones y puede evitarte errores de control con valores nulos. No tienes porque poner Optional a todo objeto que veas en tu código, pero es importante conocer las ventajas que aporta.

Recursos:

 

Añadir nuevo comentario