Как перестать проверять null в Java и не сойти с ума

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Проклятие null: почему мы пишем одно и то же снова и снова?

Если вы пишете на Java больше недели, вы наверняка сталкивались с NullPointerException. И, скорее всего, ваша первая реакция была: «Ага, нужно просто проверить!». Так рождаются километры кода вида:

if (user != null) {
    if (user.getAddress() != null) {
        String city = user.getAddress().getCity();
        if (city != null) {
            System.out.println(city.toUpperCase());
        }
    }
}

Этот код работает. Но он уродлив. Он превращает чтение логики в пытку, а поддержку — в кошмар. Вопрос «How do I avoid checking for nulls in Java?» на Stack Overflow собрал тысячи просмотров не случайно. Каждый Java-разработчик рано или поздно задаётся им.

Хорошая новость: избежать проверок на null не только можно, но и нужно. Java — не JavaScript, и у нас есть инструменты, чтобы писать чистый, безопасный код без «защитных» конструкций. Давайте разберём их все — от джентльменского набора до продвинутых техник.

1. Optional<T> — ваш главный союзник

Начиная с Java 8, у нас есть Optional. Это контейнер, который может содержать значение или быть пустым. Он буквально создан, чтобы избежать проверок на null и сделать код более декларативным.

Как это работает?

Вместо того чтобы возвращать null из метода, возвращайте Optional. А вместо проверок — используйте цепочки методов.

// Плохо
public String getCity(User user) {
    if (user == null) return "Unknown";
    Address address = user.getAddress();
    if (address == null) return "Unknown";
    return address.getCity();
}

// Хорошо
public Optional<String> getCity(User user) {
    return Optional.ofNullable(user)
            .map(User::getAddress)
            .map(Address::getCity);
}

// Использование
String city = getCity(someUser).orElse("Unknown");

Обратите внимание: ни одного if. map работает только если значение не null. Если на любом этапе встречается null — цепочка прерывается, и вы получаете Optional.empty().

Популярные методы Optional

  • orElse(T other) — вернуть значение или запасной вариант
  • orElseGet(Supplier<? extends T> other) — ленивая версия, вычисляется только если нужно
  • orElseThrow(Supplier<? extends X> exceptionSupplier) — выбросить своё исключение
  • ifPresent(Consumer<? super T> consumer) — выполнить действие, если значение есть
  • filter(Predicate<? super T> predicate) — отфильтровать по условию
// Пример с фильтром
Optional<String> name = Optional.of("Java");
name.filter(n -> n.startsWith("J"))
    .ifPresent(System.out::println); // Выведет "Java"

Важно: Optional предназначен для возвращаемых значений, а не для полей класса или параметров методов. Не злоупотребляйте.

2. Objects.requireNonNull() — утилита для явных контрактов

Иногда null — это ошибка, и её нужно обнаружить как можно раньше. Вместо того чтобы писать проверки вручную, используйте java.util.Objects.requireNonNull().

public class UserService {
    private final UserRepository repository;

    public UserService(UserRepository repository) {
        this.repository = Objects.requireNonNull(repository, 
            "UserRepository must not be null");
    }

    public User findById(Long id) {
        Objects.requireNonNull(id, "ID must not be null");
        return repository.findById(id);
    }
}

Этот подход называется fail-fast (быстрое обнаружение ошибок). Вы получаете NullPointerException с понятным сообщением сразу при создании объекта, а не через 100 строк кода.

3. Паттерн Null Object — когда null не нужен

Паттерн Null Object — это объект-заглушка, который реализует тот же интерфейс, но не делает ничего (или возвращает значения по умолчанию). Это позволяет полностью избежать null.

// Интерфейс
public interface Logger {
    void log(String message);
}

// Реальная реализация
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

// Null Object
public class NullLogger implements Logger {
    @Override
    public void log(String message) {
        // Ничего не делаем
    }
}

// Использование
public class Application {
    private final Logger logger;

    public Application(Logger logger) {
        this.logger = logger != null ? logger : new NullLogger();
    }

    public void run() {
        logger.log("Application started");
        // Работает без проверок на null
    }
}

Теперь Application никогда не хранит null. Вместо этого — специальный объект, который безопасно «ничего не делает». Паттерн особенно полезен для коллекций, слушателей и конфигураций.

4. Stream API — фильтрация без проверок

Работа с коллекциями — ещё один источник бесконечных проверок. Stream API позволяет обрабатывать списки, не беспокоясь о null внутри.

List<String> names = Arrays.asList("Alice", null, "Bob", null, "Charlie");

// Без Stream — куча if
List<String> validNames = new ArrayList<>();
for (String name : names) {
    if (name != null) {
        validNames.add(name.toUpperCase());
    }
}

// Со Stream — элегантно
List<String> result = names.stream()
        .filter(Objects::nonNull)
        .map(String::toUpperCase)
        .collect(Collectors.toList());

System.out.println(result); // [ALICE, BOB, CHARLIE]

Метод Objects::nonNull — это ссылка на статический метод Objects.nonNull(). Он работает как предикат, отбрасывающий null.

Если нужно сохранить null, но заменить его значением по умолчанию — используйте map с Optional:

List<String> safeNames = names.stream()
        .map(name -> Optional.ofNullable(name).orElse("UNKNOWN"))
        .collect(Collectors.toList());
// [ALICE, UNKNOWN, BOB, UNKNOWN, CHARLIE]

5. Аннотации @Nullable и @NonNull — контракты для разработчика

Хотя аннотации не дают гарантий на уровне компилятора (без специальных инструментов), они делают код самодокументируемым. Используйте javax.annotation.Nullable или org.jetbrains.annotations.NotNull.

import javax.annotation                

Блоги

Рекомендаций по книгам