Optional<T>
Valores potencialmente ausentes sin null
El problema de null
null en Java no distingue entre "no existe", "no se ha cargado todavía" y "hubo un error".
Optional<T> hace explícita en el tipo de retorno la posibilidad de ausencia, obligando al llamador
a tratarla.
// El problema clásico: null como ausencia de valor
public String obtenerCiudad(Usuario usuario) {
if (usuario != null) {
Direccion dir = usuario.getDireccion();
if (dir != null) {
return dir.getCiudad(); // puede ser null igualmente
}
}
return "desconocida";
}
// Esto es el "billion-dollar mistake" de Tony Hoare Crear un Optional
import java.util.Optional;
// Optional.of: el valor NO puede ser null (lanza NullPointerException si lo es)
Optional<String> conValor = Optional.of("Madrid");
// Optional.ofNullable: puede ser null; si lo es, crea un Optional vacío
String posibleNull = obtenerCiudadDeDB(); // puede devolver null
Optional<String> seguro = Optional.ofNullable(posibleNull);
// Optional.empty: sin valor
Optional<String> vacio = Optional.empty(); Extraer el valor
Preferir orElse, orElseGet u orElseThrow a get(). Un
get() sin comprobar isPresent() antes es tan peligroso como el null
que se intenta evitar.
Optional<String> ciudad = Optional.of("Barcelona");
Optional<String> sinCiudad = Optional.empty();
// get(): lanza NoSuchElementException si está vacío — evitar sin comprobar
String c1 = ciudad.get(); // "Barcelona"
// orElse: valor por defecto si vacío (siempre se evalúa)
String c2 = sinCiudad.orElse("desconocida"); // "desconocida"
// orElseGet: valor por defecto lazy (solo se evalúa si es necesario)
String c3 = sinCiudad.orElseGet(() -> calcularCiudadPorDefecto());
// orElseThrow: lanza excepción si vacío
String c4 = sinCiudad.orElseThrow(() -> new IllegalStateException("sin ciudad"));
// isPresent / isEmpty (Java 11+)
if (ciudad.isPresent()) { System.out.println(ciudad.get()); } Transformar sin desempaquetar
map, filter y flatMap permiten encadenar operaciones sobre el valor interno
sin salir del Optional ni comprobar isPresent() manualmente.
// map: transforma el valor si presente, no hace nada si vacío
Optional<String> ciudad = Optional.of(" Madrid ");
Optional<String> limpia = ciudad.map(String::trim); // Optional["Madrid"]
Optional<Integer> longitud = ciudad.map(String::length); // Optional[9]
// filter: descarta el valor si no cumple el predicado
Optional<String> larga = ciudad.filter(c -> c.length() > 5); // vacío si no cumple
// flatMap: como map pero cuando la función ya devuelve Optional
// Evita Optional<Optional<T>>
Optional<Usuario> usuario = buscarUsuario(id);
Optional<String> ciudadUsuario = usuario.flatMap(u -> Optional.ofNullable(u.getCiudad())); Ejemplo real
// Reescritura del ejemplo inicial con Optional
public Optional<String> obtenerCiudad(Optional<Usuario> usuario) {
return usuario
.map(Usuario::getDireccion) // Optional<Direccion>
.map(Direccion::getCiudad); // Optional<String>
}
// Uso
String ciudad = obtenerCiudad(Optional.ofNullable(usuario))
.orElse("desconocida"); Cuándo NO usar Optional
// MAL: Optional como campo de una clase (no serializable, overhead)
public class Persona {
private Optional<String> telefono; // incorrecto
}
// BIEN: null en los campos, Optional solo en el retorno de métodos
public class Persona {
private String telefono; // puede ser null internamente
public Optional<String> getTelefono() {
return Optional.ofNullable(telefono);
}
}