La maldición de null: ¿por qué escribimos lo mismo una y otra vez?
\n\nSi escribes en Java más de una semana, seguramente te has encontrado con NullPointerException. Y, muy probablemente, tu primera reacción fue: «¡Ajá, solo necesito comprobarlo!». Así nacen kilómetros de código como:
\n\nif (user != null) {\n if (user.getAddress() != null) {\n String city = user.getAddress().getCity();\n if (city != null) {\n System.out.println(city.toUpperCase());\n }\n }\n}\n\n\nEste código funciona. Pero es feo. Convierte la lectura de la lógica en un suplicio y el mantenimiento en una pesadilla. La pregunta «How do I avoid checking for nulls in Java?» en Stack Overflow ha acumulado miles de visitas no por casualidad. Todo desarrollador Java tarde o temprano se la plantea.
\n\nLa buena noticia: evitar las comprobaciones de null no solo es posible, sino necesario. Java no es JavaScript, y tenemos herramientas para escribir código limpio y seguro sin construcciones «defensivas». Analicémoslas todas, desde el conjunto básico hasta las técnicas avanzadas.
\n\n1. Optional<T> — tu principal aliado
\n\nDesde Java 8, tenemos Optional. Es un contenedor que puede contener un valor o estar vacío. Está literalmente diseñado para evitar comprobaciones de null y hacer el código más declarativo.
¿Cómo funciona?
\n\nEn lugar de devolver null desde un método, devuelve un Optional. Y en lugar de comprobaciones, usa cadenas de métodos.
// Malo\npublic String getCity(User user) {\n if (user == null) return "Unknown";\n Address address = user.getAddress();\n if (address == null) return "Unknown";\n return address.getCity();\n}\n\n// Bueno\npublic Optional<String> getCity(User user) {\n return Optional.ofNullable(user)\n .map(User::getAddress)\n .map(Address::getCity);\n}\n\n// Uso\nString city = getCity(someUser).orElse("Unknown");\n\n\nObserva: ni un solo if. map solo funciona si el valor no es null. Si en cualquier paso aparece un null, la cadena se interrumpe y obtienes Optional.empty().
Métodos populares de Optional
\n\n- \n
orElse(T other)— devuelve el valor o una alternativa \n orElseGet(Supplier<? extends T> other)— versión perezosa, se calcula solo si es necesario \n orElseThrow(Supplier<? extends X> exceptionSupplier)— lanza tu propia excepción \n ifPresent(Consumer<? super T> consumer)— ejecuta una acción si el valor está presente \n filter(Predicate<? super T> predicate)— filtra según una condición \n
// Ejemplo con filtro\nOptional<String> name = Optional.of("Java");\nname.filter(n -> n.startsWith("J"))\n .ifPresent(System.out::println); // Imprime "Java"\n\n\nImportante: Optional está diseñado para valores de retorno, no para campos de clase o parámetros de métodos. No abuses de él.
2. Objects.requireNonNull() — utilidad para contratos explícitos
\n\nA veces null es un error y debe detectarse lo antes posible. En lugar de escribir comprobaciones manualmente, usa java.util.Objects.requireNonNull().
public class UserService {\n private final UserRepository repository;\n\n public UserService(UserRepository repository) {\n this.repository = Objects.requireNonNull(repository, \n "UserRepository must not be null");\n }\n\n public User findById(Long id) {\n Objects.requireNonNull(id, "ID must not be null");\n return repository.findById(id);\n }\n}\n\n\nEste enfoque llama