Optional
La clase Optional<T> es la solución de Java para indicar referencias que podrían ser nulas a nivel de código, como alternativa a:
-
El operador Elvis presente en múltiples lenguajes:
String version = computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN"; -
El indicador de tipos opcionales presente en lenguajes con mayor null-safety como Kotlin:
String? version = null
Se recomienda no usarlos como argumentos de métodos (p.e. print(<Optional<String>> maybe)) ni usarlos como parámetros en genéricos (p.e. List<Optional<String>> o Map<String, Optional<String>>) ya que se podrían enviar nulos de todos modos, y porque en ambos casos también se contraindica el uso de nulos.
Crear objetos Optional
Optional.ofNullable(T value)es la versión más usada, recibiendo un objeto cualquiera sea o no nulo.Optional.empty()para un opcional sin valor.Optional.of(T value)recibe un objeto, lanzandoNullPointerExceptionsi es nulo.
¿Tengo un valor en mi Optional?
El método isPresent() retornará true si tenemos un valor en nuestro Opcional (es decir, es “no nulo”).
Desde Java 11 disponemos del método isEmpty() que retorna el valor opuesto.
Se usa el término “presente” para indicar si un Optional tiene o no valor.
Operador Elvis: map
Para transformar nuestro Optional, disponemos del método map(Function<? super T, U> mapper), que se aplica de manera un poco más verbosa pero parecida a un operador Elvis:
Optional<String> versionOptional = Optional.ofNullable(computer)
.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion);
Otras operaciones intermedias
filter(Predicate<? super T> predicate): elOptionalusará elPredicatesi tiene valor, pasando a estar vacío si este retornafalse.// Admitir sólo versiones "válidas" versionOptional = versionOptional.filter(version -> version.matches("[0-9]\\.[0-9]"));flatMap(Function<? super T, Optional<U>> mapper)): similar amap, pero elOptionalretornado se “aplana” para seguir tratando con unOptional<T>en vez deOptional<Optional<T>>.Optional<String> versionOptional = Optional.ofNullable(computer) .map(Computer::getSoundcard) .flatMap(Soundcard::getMaybeUSB) // Optional<USB> getMaybeUSB() .map(USB::getVersion);
Operaciones finales: resolver un Optional
Para recuperar el valor dentro de un Optional, tenemos varias opciones:
get(): la opción más simple; se recomienda no usarla porque en caso de fallo se limitará a lanzarNoSuchElementExceptioncon el mensaje genérico de error “No value present”, lo que hace que en código que useOptionalen muchos puntos los errores sean difíciles de trazar.String version = versionOptional.get();orElse(T other): retorna el valor si está presente, o el objeto indicado si no.String version = versionOptional.orElse("");orElseGet(Supplier<T> other): retorna el valor si está presente, o el objeto retornado por elSuppliersi no; esto es útil cuando queremos que no se genere la alternativa si no es necesaria (por ejemplo, que requiera una lectura de una base de datos alternativa o tarde mucho).String version = versionOptional.orElseGet(() -> computer.getVersionAlt());orElseThrow(Supplier<X extends Throwable> exceptionSupplier): retorna el valor si está presente, o lanza la excepción generada por elSupplier; esta es la alternativa agetque nos permitirá tener errores más entendibles.String version = versionOptional.orElseThrow(() -> new NoSuchElementException( "Could not get soundcard USB version. Make sure a valid soundcard is connected");ifPresent(Consumer<? super T> consumer): ejecuta elConsumersólo si el valor está presente.versionOptional.ifPresent(version -> System.out.println("Soundcard USB version: " + version);
Ejercicios
Crea una clase que extienda a ArrayList<Integer> y, usando Optional:
- Sobreescribe el método
addpara retornarfalsesi se intenta añadir un nulo o un número negativo. - Implementa un método
tryAddDouble(Double double)que llame aaddcon el valor entero deDoublesi no tiene parte decimal - Implementa un método
Optional<Integer> getMaybe(int index)que:- Retorne un opcional vacío si el índice se sale de la lista.
- Lance
IllegalArgumentExceptioncon mensaje “Invalid index {index}” si se pasa un índice negativo.
- Implementa un método
void printValue(int index)que, usando el método anterior, imprima el par índice-valor si está presente.