La clase Optional, introducida en Java 8, es una herramienta que nos ayuda a manejar valores que pueden ser nulos de una forma mucho más segura y explícita. Aunque lleva ya bastante tiempo entre nosotros, todavía hay muchos desarrolladores que no conocen su existencia o no saben cómo sacarle partido. Optional es la versión que Java ofrece de lo que en otros lenguajes se conoce como la mónada Maybe, una estructura que nos permite indicar claramente que un valor puede estar presente o no.
En Java, es común que los métodos devuelvan null para indicar la ausencia de un valor. Por ejemplo, imaginemos un método getFactura que recibe un día de la semana y devuelve una factura si es un día laborable, pero devuelve null si es sábado o domingo, porque no se emiten facturas esos días. El problema con null es que es muy fácil olvidarse de comprobar si el valor es nulo antes de usarlo, lo que puede provocar errores como el temido NullPointerException. Esto ocurre cuando intentamos llamar a un método sobre una referencia que en realidad apunta a null.
Optional nos propone una solución para evitar estos problemas. En lugar de devolver null, podemos devolver un objeto Optional que siempre estará presente, pero que puede contener o no un valor. Podemos imaginar Optional como una caja cerrada: siempre tenemos la caja, pero a veces está vacía y otras veces contiene un valor. Esto hace que el código sea más explícito y seguro, porque ya no trabajamos con referencias que pueden ser null, sino con objetos Optional que nos obligan a manejar la posibilidad de ausencia de valor.
Para crear un Optional, disponemos de tres métodos estáticos principales: empty(), of() y ofNullable(). El método empty() nos devuelve un Optional vacío, es decir, una caja sin contenido. El método of() envuelve un valor no nulo dentro de un Optional, y si intentamos pasarle un null, lanzará una excepción. Por último, ofNullable() es una forma práctica que envuelve un valor si no es null, o devuelve un Optional vacío si el valor es null.
Veamos un ejemplo sencillo de cómo podríamos usar Optional en nuestro método getFactura:
import java.util.Optional;
public class ProveedorOpcional {
private ProveedorFactura proveedorFactura = new ProveedorFactura();
public Optional<Factura> getFactura(String diaSemana) {
if (diaSemana.equalsIgnoreCase("sábado") || diaSemana.equalsIgnoreCase("domingo")) {
return Optional.empty();
} else {
Factura factura = proveedorFactura.getFactura(diaSemana);
return Optional.of(factura);
}
}
}
Al usar Optional, el código que consume este método debe adaptarse. Ya no podemos llamar directamente a métodos de la factura sin antes comprobar si el Optional contiene un valor. Para ello, Optional nos ofrece varios métodos útiles. Por ejemplo, isPresent() devuelve true si hay un valor dentro, y isEmpty() devuelve true si está vacío. También podemos usar ifPresent() para ejecutar una acción solo si el valor está presente, pasando una función lambda que se ejecutará en ese caso.
Si queremos obtener el valor dentro del Optional, podemos usar get(), pero hay que tener cuidado porque si el Optional está vacío, lanzará una excepción. Por eso, es más recomendable usar métodos como orElse() o orElseThrow(). El método orElse() nos permite proporcionar un valor alternativo que se devolverá si el Optional está vacío, evitando así excepciones. Por ejemplo:
Factura factura = proveedorOpcional.getFactura("domingo")
.orElse(new Factura("Factura por defecto", 0.0));
En este caso, si no hay factura para el domingo, obtendremos una factura por defecto en lugar de un error.
Además, Optional se integra muy bien con la API de streams de Java 8, permitiendo usar métodos como filter(), map() o flatMap() para transformar o filtrar los valores de forma funcional y elegante.
En definitiva, Optional nos ofrece una forma más clara y segura de manejar valores que pueden estar ausentes, evitando los problemas clásicos asociados al uso de null. Aunque podemos seguir usando null si queremos, Optional nos proporciona una API más precisa para expresar la intención de que un valor puede no estar presente, y nos ayuda a escribir código más robusto y legible.