Optional
Определение
Optional<T> — контейнер, в котором есть одно ненулевое значение либо нет значения. Он делает отсутствие частью возвращаемого типа:
Optional<User> findById(long id) {
return Optional.ofNullable(storage.get(id));
}Это особенно удобно для поиска, где «не найдено» — нормальный исход, а не исключительная ошибка.
Создание
Optional<String> empty = Optional.empty();
Optional<String> present = Optional.of("ready");
Optional<String> maybe = Optional.ofNullable(nullableValue);Optional.of(null) сразу выбрасывает NullPointerException. Сам Optional тоже не должен быть null: это разрушает двойственный контракт.
Преобразование
String email = repository.findById(id)
.filter(User::active)
.map(User::email)
.map(String::trim)
.orElse("unknown");filterоставляет значение при выполнении условия;mapпреобразует значение и превращаетnull-результат mapper в empty;flatMapнужен, когда функция уже возвращаетOptional, чтобы не получить вложенный контейнер.
Optional<Address> address = findUser(id)
.flatMap(User::primaryAddress);Получение значения
User cachedOrLoaded = cache.find(id)
.orElseGet(() -> repository.load(id));orElse(value)вычисляет аргумент до вызова, даже если значение есть;orElseGet(supplier)вычисляет запасной вариант лениво;orElseThrow()выбрасываетNoSuchElementException;orElseThrow(factory)создаёт доменную ошибку;ifPresent,ifPresentOrElseвыполняют ветви как эффекты.
Прямой get() без доказанного presence обычно возвращает код к ручным проверкам и исключениям.
Optional в API
Рабочее правило: Optional полезен как возвращаемый тип, когда отсутствие ожидаемо. Для параметра обычно яснее перегрузка, отдельный тип команды или явный nullable-контракт.
Для полей entity/DTO/сериализуемых моделей Optional часто неудобен:
- сам
Optionalне реализуетSerializable; - ORM и serializers могут иметь специальные или несовместимые правила;
- контейнер добавляет состояние «поле Optional равно null», которого быть не должно.
Внутри одной реализации nullable-переменная иногда проще. Optional ценен на границе контракта, а не как обязательная оболочка каждого значения.
Primitive Optional
OptionalInt, OptionalLong, OptionalDouble избегают boxing:
OptionalInt max = values.stream()
.mapToInt(Item::priority)
.max();У них меньше combinator-методов, поэтому выбор зависит от читаемости и объёма данных.
Исключение или empty
empty— допустимое отсутствие (findByIdничего не нашёл);- исключение — нарушение контракта, недоступность ресурса или другой ошибочный исход.
Нельзя превращать любую ошибку БД в Optional.empty: тогда «не найдено» становится неотличимо от сбоя.
Ловушки
- Возвращать
nullвместоOptional.empty. - Вызывать
get()без проверки. - Использовать
orElse(expensiveCall())вместоorElseGet. - Делать
Optional<Optional<T>>вместоflatMap. - Прятать инфраструктурную ошибку как empty.
- Использовать Optional в каждом поле и параметре без причины.
Вопросы на собеседовании
- Чем
ofотличается отofNullable? - В чём разница между
mapиflatMap? - Почему
orElseможет выполнить лишнюю работу? - Где Optional уместен в публичном API?
- Нужно ли возвращать empty при ошибке БД?
- Для чего существуют
OptionalInt,OptionalLong,OptionalDouble?
Связанные заметки
Функциональные интерфейсы · ФИ стандартной библиотеки Java · Stream API · Работа с исключениями в Java · Классы обертки Java