A maldição do null: por que escrevemos a mesma coisa repetidamente?
\n\nSe você programa em Java há mais de uma semana, certamente já encontrou um NullPointerException. E, muito provavelmente, sua primeira reação foi: "Ah, é só verificar!". Assim nascem quilô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\nEsse código funciona. Mas é feio. Ele transforma a leitura da lógica em um suplício e a manutenção em um pesadelo. A pergunta "How do I avoid checking for nulls in Java?" no Stack Overflow acumulou milhares de visualizações não por acaso. Todo desenvolvedor Java, mais cedo ou mais tarde, se faz essa pergunta.
\n\nA boa notícia: evitar verificações de null não só é possível, como é necessário. Java não é JavaScript, e temos ferramentas para escrever código limpo e seguro sem construções "defensivas". Vamos analisar todas elas — desde o conjunto básico até técnicas avançadas.
\n\n1. Optional<T> — seu principal aliado
\n\nA partir do Java 8, temos o Optional. É um contêiner que pode conter um valor ou estar vazio. Ele foi literalmente criado para evitar verificações de null e tornar o código mais declarativo.
Como funciona?
\n\nEm vez de retornar null de um método, retorne Optional. E em vez de verificações, use encadeamento de métodos.
// Ruim\npublic String getCity(User user) {\n if (user == null) return "Desconhecido";\n Address address = user.getAddress();\n if (address == null) return "Desconhecido";\n return address.getCity();\n}\n\n// Bom\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(algumUsuario).orElse("Desconhecido");\n\n\nObserve: nenhum if. O map só funciona se o valor não for null. Se em qualquer etapa encontrar null — a cadeia é interrompida e você obtém Optional.empty().
Métodos populares do Optional
\n\n- \n
orElse(T other)— retornar o valor ou uma alternativa \n orElseGet(Supplier<? extends T> other)— versão lazy, calculada apenas se necessário \n orElseThrow(Supplier<? extends X> exceptionSupplier)— lançar sua própria exceção \n ifPresent(Consumer<? super T> consumer)— executar uma ação se o valor existir \n filter(Predicate<? super T> predicate)— filtrar por uma condição \n
// Exemplo com filtro\nOptional<String> nome = Optional.of("Java");\nnome.filter(n -> n.startsWith("J"))\n .ifPresent(System.out::println); // Exibirá "Java"\n\n\nImportante: Optional foi projetado para valores de retorno, não para campos de classe ou parâmetros de métodos. Não abuse.
2. Objects.requireNonNull() — utilitário para contratos explícitos
\n\nÀs vezes, null é um erro e precisa ser detectado o mais cedo possível. Em vez de escrever verificações manualmente, use 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 não deve ser nulo");\n }\n\n public User findById(Long id) {\n Objects.requireNonNull(id, "ID não deve ser nulo");\n return repository.findById(id);\n }\n}\n\n\nEssa abordagem chama