java.util.Optional – Un pequeño tutorial práctico

0
21423

Java 8 introdujo la clase java.util.Optional, basada en la popular clase de Guava del mismo nombre. Se dice que nunca deberíamos llamar al método get(). En este tutorial veremos algunos ejemplos de alternativas al get().

[box style=»1″]

Éste artículo es una traducción al castellano de la entrada original publicada, en inglés, por Dr. Heinz Kabutz en su número 238 del JavaSpecialists newsletter. Puedes consultar el texto original en Javaspecialists’ Newsletter #238: java.util.Optional – Short Tutorial By Example

Este artículo se publica en Adictos al Trabajo, con permiso del autor, traducido por David Gómez García, (@dgomezg) consultor tecnológico en Autentia, colaborador de Javaspecialists e instructor certificado para impartir los cursos de Javaspecialists en Español.
[/box]

Bienvenidos a la edición número 238 del Javatmspecialists’ Newsletter, escrita a medias entre en el viaje de vuelta de JAX Finance de Londres y otro vuelo desde Grecia a España, porque estaré unos días en Málaga y donde además, he sido invitado a dar una charla el 10 de Mayo de 2016 en el Málaga User Group. Me gusta participar en los JUGs cuando viajo, porque me permite conocer gente que de verdad siente pasión por Java y por el aprendizaje constante.

Hace unas semanas, Sanjeev Kumar me sugirió que, como realmente disfrutaba de mis videos educativos en Vimeo, podría tratar de grabar un pequeño mensaje en cada newsletter. Me gusta la idea y he comenzado a hacerlo. Espero que os resulte interesante. Por favor, decidme si lo es y si os gustaría seguir viendo más videos.


Echa un vistazo al nuevo curso «Extreme Java», que combina concurrencia, un poquito de rendimiento y Java 8: Extreme Java – Concurrency & Performance for Java 8, y que también los impartimos en Español.

java.util.Optional – Un pequeño tutorial práctico.

En mis investigaciones, me centro principalmente en el propio Java SE. Aunque también echo un vistazo a muchos otros frameworks como Spring, Guava, Eclipse Collections o JEE, no se quedan almacenados en la memoria a largo plazo de mi cerebro. Desafortunadamente, no he dedicado mucho tiempo al Optional de Google Guava, así que cuando java.util.Optional llegó con Java 8, me resultaba bastante novedoso. La gracia de Optional y de sus primos OptionalInt, OptionalDouble y OptionalLong está en que evitan que un método devuelva null. Un noble objetivo.

Deberíamos evitar invocar al método get() y utilizar en su lugar otros métodos que os mostraré en un segundo. Si tenemos que utilizar get(), tendríamos que comprobar primero si hay valor. Un get() que no va precedido de una llamada a isPresent() es un bug. Dado que tenemos alternativas a get() para los casos más habituales, Stuart Marks ha propuesto no hace mucho que get() sea deprecado y sustituido con el método getWhenPresent(). ¡Creo que sólo Brian Goetz está de acuerdo con él! Afortunadamente no sucederá, porque tampoco creo que getWhenPresent() sea un buen nombre. ¿Qué sucede si no está presente? ¿Qué hacemos? ¿Se bloquea?. ¡Oh! ¡Si lanza un NoSuchElementException!. Entonces, ¿por qué no llamarlo getWhenPresentOrElseThrowNoSuchElementExceptionIfYouCallIt()? Un cambio así en Java es muy pesado. Para empezar, es necesario añadir el nuevo método y deprecar el antiguo método get(). Entonces, habrá que esperar, al menos una release para eliminar get() definitivamente. Entre tanto, deberemos cambiar todo nuestro código que llama correctamente a get() después de haber comprobado que el valor existe con isPresent() para utilizar en su lugar el nuevo nombre, o alternativamente, anotar nuestro código con @SupressWarnings("unchecked"). O también podemos ignorar los avisos de usos de métodos obsoletos, como hemos estado haciendo los últimos 20 años.

Stuart Marks me envió un artículo muy interesante, donde muestra ejemplos del JDK donde se utiliza Optional.get() y cómo podría haber sisdo reemplazado con otros mecanismos. Muy amablemente, me permitió publicar sus conclusiones en este newsletter. Espero que resulte un «tutorial práctico» para aquellos que no han usado Optional antes y quiere conocer cómo funciona.

El primer método que debemos conocer es Optional.ifPresent(Consumer action). No isPresent(), sino ifPresent(). Debo admitir que cuando vi su referencia a este método me rasqué la cabeza pensando si realmente exisitía. Había visto isPresent antes, pero no me había percatado que también teníamos otro método con casi el mismo nombre. Al final, «s» y «f» sólo están separadas por una «d» :-). Así que, si estás tentando a escribir código como el siguiente:

	if (source.isPresent()) {
	  doSomethingWith(source.get());
	}

mientras doSomethingWith() no lance ninguna checked exception, podrías transformar el código en:

	source.ifPresent(s -> doSomethingWith(s));

O si, como a mí, no te asustan las referencias a métodos, puedes hacerlo así:

	source.ifPresent(this::doSomethingWith);

Stuart curioseó en la JDK y encontró un puñado de ejemplos donde se podría haber utilizado esta estructura. Recuerda, se trata de los desarrolladores de la JDK, por tanto seguramente no desarrolladores totalmente novatos. El primer ejemplo es de DependencyFinder.java:

	if (source.isPresent()) {
	    executor.runTask(source.get(), deque);
	}

que podía re-escribirse como

	source.ifPresent(archive -> executor.runTask(archive, deque));

El siguiente ejemplo es de JdepsTask.java:

	Optional req = options.requires.stream()
	  .filter(mn -> !modules.containsKey(mn))
	  .findFirst();
	if (req.isPresent()) {
	  throw new BadArgs("err.module.not.found", req.get());
	}

que podría ser escrito como:

	options.requires.stream()
  .filter(mn -> !modules.containsKey(mn))
  .findFirst()
  .ifPresent(s -> throw new BadArgs("err.module.not.found", s));

El siguiente es un fragmento de código en el que el programador no comprobó que el Optional contenía un valor utilizando isPresent(). Si, por alguna razón, la subList() está vacía, el método reduce() devolverá un Optional vacío y por tanto get() lanzará un NoSuchElementException. Los IDEs deberían darse cuenta y avisarnos. Pero además, tenemos una forma más breve y probablemente más eficiente de fusionar Strings con String.join(). El código procede esta vez de JShellTool.java:

	String hist = replayableHistory
	  .subList(first + 1, replayableHistory.size())
	  .stream()
	  .reduce( (a, b) -> a + RECORD_SEPARATOR + b)
	  .get();

que podría escribirse así:

	String hist = String.join(RECORD_SEPARATOR,
  replayableHistory.subList(first+1, replayableHistory.size()))

Otro pequeño fragmento de código extraido de Resolver.java

	if (mref.location().isPresent())
	    trace("  (%s)", mref.location().get());

que podía escribirse como

	mref.location().ifPresent(loc -> trace("  (%s)", loc);

El siguiente es un poco diferente. En este caso, comprueban si un determinado valor no está presente. En lugar de hacerlo de esta forma, podemos filtrar primero y confirmar si existe algún valor que cumpla nuestros requisitos. Si no, o nunca hubo valor, lanzamos una excepción utilizando Optional.orElseThrow(). Este ejemplo es de la nueva clase Layer de Java 9 en el paquete Reflection. Layer.java:

Optional<Configuration> oparent = cf.parent();
if (!oparent.isPresent() || oparent.get() != this.configuration()) {
  throw new IllegalArgumentException(
    "Parent of configuration != configuration of this Layer");

que podría escribirse como

	cf.parent()
  .filter(cfg -> cfg == this.configuration())
  .orElseThrow(() -> new IllegalArgumentException(
    "Parent of configuration != configuration of this Layer"));

Creo que estos ejemplos son muy interesantes y de utilidad para comprender cómo debería utilizarse Optional. En cualquier caso, qué sucede si nuestra manera habitual es un pelín distinta, como por ejemplo del estilo a:

	Optional<BigInteger> prime = findPrime();
if (prime.isPresent()) {
  System.out.println("Prime is " + prime.get());
} else {
  System.out.println("Prime not found");
}

Le planteé esta pregunta a Stuart Marks y me respondió con una solución elegante que utilizaba map() y orElse(). En nuestro ejemplo, map transforma los BigInteger en String. Pero, si el Optional está vacío, simplemente retornará un Optional<String>. Podemos incluso devlover un valor por defecto si está vacío ("Prime not found") o podemos lanzar una excepcion. Éste es el código:

	System.out.println(
  findPrime()
    .map(p -> "Prime is " + p)
    .orElse("Prime not found"));

Le sugerí a Stuart que quizá también deberíamos añadir Optional.ifPresentElse(Consumer,Runnable). Resulta que ya lo incluirá Java 9, junto con otros nuevos métodos que convierten Optional en un composite:

public void ifPresentOrElse(Consumer action,
                            Runnable emptyAction);
public Optional or(Supplier<Optional> supplier);
public Stream stream();

Os recomiendo fervientemente utilizar Optional en vuestro código, porque reduce la posibilidad de un problema muy común: NullPointerException. Y, si te tienta utilizar get(), utiliza los consejos de este artículo como guía de buenas prácticas.

Un Saludo

Heinz.

Heinz es el creador de "The Java Specialists' Newsletter".
Doctor en Informática, Heinz ha participado en el desarrollo de grandes aplicaciones en Java, ha formado a miles de desarrolladores profesionales y es ponente habitual en las principales conferencias sobre Java.
Reconocido como Java Champion por Sun Microsystems -los creadores de Java- por su trabajo en mejorar Java, Heinz imparte cursos de JavaSpecialists.eu por todo el mundo, de forma remota y presencial. Es autor de dichos cursos, incluidos 'Java Specialists Master', 'DesignPatterns and Concurrency Specialists' y 'Performance and Concurrency for Java 8'.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad