Java Optional

por


Publicado en: marzo 14, 2018



En este artículo vamos a explicar el concepto general y uso básico del Optional que se introdujo en los API de Java 8.

Optional Type

En Java 8 se introdujo una nueva clase llamada java.util.Optional<T>. Algunas personas asumen que su introducción se dió para eliminar los NullPointerExceptions del código; pero esa no es su razón de ser. En realidad, Optional se diseño para comunicar al usuario cuando un valor que se retorna puede ser legitimamente null.

Dicho esto, una instancia de Optional puede estar en uno de dos estados: una referencia a una instancia del tipo T (conocido como presente), o vacío conocido como empty en contraposición a null.

Utilización


Para emplear Optional debemos tomar en cuenta lo siguiente:

  • Sus instancias son inmutables (aunque pueden tener referencias a objetos mutables).
  • No tiene un constructor público, por lo que debemos instanciarlas por medio de un factory
  • Implementa los métodos equals, hashCode y toString

Para poder instanciar un Optional, podemos hacer uso de los siguientes métodos:

  • empty() : Retorna un Optional vacio.
  • of(T value) : Retorna un Optional que envuelve el valor que se pasa como parámetro. Nunca le envies un null; pues eso generaría una excepción.
  • ofNullable(T value) : Retorna un Optional que es vacio si el parámetro es null, o bien envuelve el valor que se para como parámetro.

Observemos este código:

    Long valor = null;
    Optional<Long> opt = Optional.ofNullable(valor);  // Creamos un Optional, pasando un null


    System.out.println(opt);                          // Esto retorna Optional.empty


    valor = 8L;
    opt = Optional.ofNullable(valor);                 // Ahora, creamos un optional con el valor 8

    System.out.println(opt);                          // Esto nos diria: Optional[8]

    System.out.println(opt.get());                    // Retornaria el valor 8 de tipo Long.

Este caso es bastante sencillo; pero podemos mezclarlo con el uso de API de streams para crear cosas como buscar aquellos nombres que tengan más de 6 caracteres.

Optional<String> nombres =
                Stream.of("Gerardo", "Marvin", "Kathya", "Rodrigo", "Sergio")
                .filter(s -> s.length() > 6)
                .findFirst();

        System.out.println(nombres.get());

Si corremos este programa; nos retornaría como respuesta Gerardo; pues es el primer nombre con más de 6 caracteres.

Pero observen que hago uso del método get; eso no debe hacerse en código productivo. Por qué? Si producto del filter que aplicamos no quedaran datos, la invocación al get nos daría un: java.util.NoSuchElementException: No value present

Una manera de solucionarlo es hacer:

  System.out.println(nombres.isPresent() ? nombres.get() : "No hay nombres de esa longitud");

Aquí empleamos el método isPresent para saber si el Optional esta vacio; y ser así retornar el texto ‘No hay nombres de esa longitud’. Algunos se cuestionaran que sólo cambiamos una evaluación para saber si es null a un isPresent y la verdad eso no parece un gran avance.

Y tienen razón, para eso podemos usar otro mecanismo; que sería:

  System.out.println(nombres.orElse("No hay nombres de esa longitud"));

Este método orElse retorna el valor contenido si esta presente o en su lugar devuelve el valor por omisión que proporcionamos. Existen otros métodos similar; que listamos a continuación:

  • orElseGet(Supplier<? extends T> other) : Retorna el valor si está presente, en caso contrario invoca el ‘Supplier’ y retorna el resultado.
  • orElseThrow(Supplier<? extends X> exceptionSupplier) : Retorna el valor si está presente, en caso contrario lanza la excepción creada por el ‘Supplier’

Por ejemplo:

  Optional<Cliente> = clientes.stream.findFirst();

  clientes.orElse(new Cliente());
  clientes.orElseGet(() -> new Cliente());
  clientes.orElseThrow(NoSuchElementException::new);

Usando Streams, uno podria aplicar una función a una colección de instancias de Optional, pero solo si tienen un valor presente; a manera de ilustración:

  public List<Cliente> buscarClientes(List<Integer> ids) {
      return ids.stream()
        .map(this::buscarClientePorId)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());
  }
Conclusión

Esperamos que con este artículo les invite a investigar sobre la manera de introducir Optional en sus desarrollos.

Autor del Artículo

Gerardo Arroyo Arce

Gerardo Arroyo Arce

Chief Technology Officer.

  • Ingeniero en Software. ITCR.
  • Master en Computación en Informática. UCR.
  • AWS Certified Solutions Architect - Associate
  • Oracle Certified Expert, JEE 6 Web Services Developer.
  • Oracle Certified Professional, Java SE 7 Programmer.
  • Oracle WebLogic Server 12c Certified Implementation Specialist.