Stream API ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-09-16

Методы класса Optional в Java

Посмотрите на этот код и попробуйте угадать, что окажется в консоли:

public class OptionalDemo1 {

    public static void main(String[] args) {
        Optional<String> summary = Optional.of("Optional methods in Java");

        System.out.println(summary.orElse(defaultSummary()));
    }

    static String defaultSummary() {
        System.out.println("Готовлю значение по умолчанию...");
        return "Default summary";
    }
}

Значение в Optional есть, значит defaultSummary() не нужен — но в консоли будут две строки: сначала «Готовлю значение по умолчанию…», и только потом Optional methods in Java. Аргумент orElse() — это обычное выражение, и Java вычисляет его до вызова метода, независимо от того, пуст контейнер или нет. Если заменить вызов на summary.orElseGet(OptionalDemo1::defaultSummary), лишней строки не будет.

Этот урок — продолжение темы «Что такое Optional», где разобраны назначение класса и способы создания контейнера (empty(), of(), ofNullable()). Здесь мы сразу переходим к методам: что делает каждый, что он возвращает и в какой ситуации его выбирать.

Все методы Optional<T> делятся на четыре группы:

  • Созданиеempty(), of(), ofNullable(): статические фабрики, возвращают Optional.
  • Проверка наличия значенияisPresent(), isEmpty(), ifPresent(), ifPresentOrElse().
  • Извлечение значенияget(), orElse(), orElseGet(), orElseThrow(): возвращают сам объект типа T, а не обёртку.
  • Преобразованиеmap(), flatMap(), filter(), or(), stream(): возвращают новый Optional (или Stream) и потому складываются в цепочки.

1. Все методы Optional: сводная таблица

Класс java.util.Optional появился в Java 8, но с тех пор пополнялся почти в каждом крупном релизе. Подсвеченные строки — методы, которых нет в Java 8; если код должен собираться под старую версию, их использовать нельзя.

Метод Что делает Когда использовать
empty() Возвращает пустой Optional Когда значения заведомо нет: результат поиска, который ничего не нашёл
of(T value) Оборачивает значение; null вызывает NullPointerException Когда значение точно не null
ofNullable(T value) Оборачивает значение, а для null возвращает пустой Optional Когда значение может оказаться null (результат из БД, из Map)
isPresent() Возвращает true, если значение есть В условии, когда нужна именно проверка, а не извлечение
isEmpty() (Java 11+) Возвращает true, если значения нет Вместо !optional.isPresent() — читается лучше
ifPresent(Consumer) Выполняет действие, если значение есть; иначе не делает ничего Вместо связки if (isPresent()) { get() }
ifPresentOrElse(Consumer, Runnable) (Java 9+) Действие для значения и отдельное действие для пустого контейнера Когда нужны обе ветки: обработать значение или залогировать отсутствие
get() Возвращает значение или бросает NoSuchElementException Только после явной проверки; в новом коде предпочтительнее orElseThrow()
orElse(T other) Возвращает значение или переданный объект Когда значение по умолчанию уже готово: константа, литерал, пустой список
orElseGet(Supplier) Возвращает значение или результат функции, вычисленный лениво Когда значение по умолчанию дорого получить: запрос, чтение файла, new
orElseThrow() (Java 10+) Возвращает значение или бросает NoSuchElementException Замена get() с говорящим именем: видно, что метод может бросить исключение
orElseThrow(Supplier) Возвращает значение или бросает исключение, созданное функцией Когда нужно своё исключение: UserNotFoundException, IllegalStateException
filter(Predicate) Оставляет значение, если оно проходит проверку, иначе отдаёт пустой Optional Дополнительное условие внутри цепочки без if
map(Function) Применяет функцию к значению и заворачивает результат в Optional Когда функция возвращает обычный объект: String, Integer
flatMap(Function) То же, но функция сама возвращает Optional, и лишняя обёртка не появляется Цепочка методов, каждый из которых возвращает Optional
or(Supplier<Optional>) (Java 9+) Если пусто, подставляет Optional из другого источника Цепочка запасных вариантов: кеш, затем файл, затем настройки
stream() (Java 9+) Превращает контейнер в Stream из нуля или одного элемента Внутри flatMap(), чтобы отбросить пустые значения в потоке
equals(Object) Сравнивает содержимое двух контейнеров через equals() значений В тестах и сравнении результатов
hashCode() Хеш значения или ноль для пустого контейнера Отдельно почти не вызывается
toString() Даёт Optional[значение] или Optional.empty Отладка и логи, но не вывод пользователю

Полные сигнатуры и точные формулировки контрактов всегда можно свериться в официальном javadoc класса Optional.

2. Проверка значения: isPresent, isEmpty, ifPresent, ifPresentOrElse

isPresent() возвращает true, если контейнер не пуст. Это самый прямолинейный способ проверки — и самый близкий к обычному if (value != null).

Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> emptySummary = Optional.empty();

if (summary.isPresent()) {
    System.out.println(summary.get()); // Optional methods in Java
}

System.out.println(summary.isPresent());      // true
System.out.println(emptySummary.isPresent()); // false

С Java 11 есть обратный метод isEmpty(). Он не добавляет новых возможностей, но избавляет от отрицания, которое легко не заметить при чтении кода:

if (emptySummary.isEmpty()) {          // вместо if (!emptySummary.isPresent())
    System.out.println("Значения нет"); // Значения нет
}

ifPresent(Consumer) выполняет переданное действие, только если значение есть. Для пустого контейнера не происходит ничего — ни исключения, ни вывода:

summary.ifPresent(System.out::println);      // Optional methods in Java
emptySummary.ifPresent(System.out::println); // тишина, никакого NullPointerException

Если нужна и вторая ветка — до Java 9 приходилось писать if/else вокруг isPresent(). Теперь есть ifPresentOrElse(Consumer, Runnable): первый аргумент получает значение, второй выполняется, когда значения нет.

summary.ifPresentOrElse(
        s -> System.out.println("Найдено: " + s),
        () -> System.out.println("Значение не задано"));
// Найдено: Optional methods in Java

emptySummary.ifPresentOrElse(
        s -> System.out.println("Найдено: " + s),
        () -> System.out.println("Значение не задано"));
// Значение не задано

Обратите внимание на типы аргументов: Consumer<T> принимает значение, а Runnable ничего не принимает — принимать ему нечего, контейнер пуст.

3. Извлечение значения: get, orElse, orElseGet, orElseThrow

Все четыре метода возвращают объект типа T, то есть выводят вас из мира Optional обратно в обычный код. Разница — в том, что происходит, когда значения нет.

get()

get() отдаёт значение как есть, а на пустом контейнере бросает NoSuchElementException:

Optional<String> emptySummary = Optional.empty();
System.out.println(emptySummary.get());
// Exception in thread "main" java.util.NoSuchElementException: No value present

Именно поэтому вызов get() без предварительной проверки бессмыслен: он превращает NullPointerException в NoSuchElementException, но не спасает от падения.

orElse(T other)

Возвращает значение, а если контейнер пуст — переданный объект:

Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> emptySummary = Optional.empty();

System.out.println(summary.orElse("Default summary"));      // Optional methods in Java
System.out.println(emptySummary.orElse("Default summary")); // Default summary

В качестве значения по умолчанию допустим и null: optional.orElse(null) — корректный способ вернуться к обычной ссылке, например при передаче результата в старый API.

orElseGet(Supplier)

То же самое, но значение по умолчанию описано функцией и вычисляется только в момент, когда оно действительно понадобилось:

System.out.println(emptySummary.orElseGet(() -> "Default summary")); // Default summary

orElseThrow()

С Java 10 у Optional есть orElseThrow() без аргументов — он делает ровно то же, что get(), но по имени метода сразу видно, что вызов может завершиться исключением:

String value = summary.orElseThrow();   // Optional methods in Java
String fail = emptySummary.orElseThrow();
// java.util.NoSuchElementException: No value present

orElseThrow(Supplier)

Версия с аргументом позволяет бросить своё исключение с осмысленным сообщением — это основной способ превратить «ничего не найдено» в ошибку бизнес-логики:

public User findUser(long id) {
    return repository.findById(id)        // Optional<User>
            .orElseThrow(() -> new UserNotFoundException("Пользователь " + id + " не найден"));
}

Важно

Метод get() не помечен как @Deprecated, поэтому компилятор на него не ругается. Но в javadoc, начиная с Java 10, прямо сказано: предпочтительная альтернатива — orElseThrow(). В новом коде используйте orElseThrow(), а get() оставьте для чтения старых проектов и вопросов на собеседовании.

4. orElse и orElseGet: в чём разница

Оба метода возвращают одно и то же значение, и в 99% случаев результат совпадает. Разница — в моменте вычисления запасного варианта:

  • orElse(T other) принимает готовый объект. Аргумент вычисляется всегда, ещё до входа в метод — даже если Optional не пуст и результат будет отброшен.
  • orElseGet(Supplier<T>) принимает функцию. Она вызывается лениво, только когда контейнер оказался пустым.

Пока справа от orElse() стоит литерал или константа, разницы нет. Она становится заметной, как только там оказывается вызов метода:

public class OptionalDemo2 {

    public static void main(String[] args) {
        Optional<String> summary = Optional.of("Optional methods in Java");

        System.out.println("--- orElse ---");
        System.out.println(summary.orElse(defaultSummary()));
        // Готовлю значение по умолчанию...
        // Optional methods in Java

        System.out.println("--- orElseGet ---");
        System.out.println(summary.orElseGet(OptionalDemo2::defaultSummary));
        // Optional methods in Java
    }

    static String defaultSummary() {
        System.out.println("Готовлю значение по умолчанию...");
        return "Default summary";
    }
}

orElse() вызвал defaultSummary() и выбросил результат. Здесь это всего лишь лишняя строка в консоли, но на практике на этом месте бывает запрос в базу, обращение к внешнему сервису или создание тяжёлого объекта — и тогда программа делает работу, результат которой никому не нужен. Хуже, если метод меняет состояние: пишет в лог, увеличивает счётчик, создаёт запись — побочный эффект произойдёт и для непустого Optional.

Практическое правило: orElse() — для готовых значений (orElse(""), orElse(0), orElse(List.of())), orElseGet() — для всего, что нужно вычислять (orElseGet(this::loadDefaults), orElseGet(ArrayList::new)).

Вопрос с собеседования

«Чем orElse() отличается от orElseGet()?» — один из самых частых вопросов по Optional. Ответ на уровне «orElseGet принимает Supplier» засчитывают редко: ждут фразы про то, что аргумент orElse() вычисляется всегда, даже когда значение в контейнере есть, и что при дорогом или небезопасном вычислении это лишняя работа и незапланированные побочные эффекты.

5. Преобразование: map, flatMap, filter

Эти три метода возвращают не значение, а новый Optional, поэтому их можно соединять в цепочку и заканчивать её одним orElse() или orElseThrow().

map(Function)

map() применяет функцию к значению, если оно есть, и заворачивает результат обратно в Optional. Если контейнер пуст, функция не вызывается вообще:

Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> emptySummary = Optional.empty();

System.out.println(summary.map(s -> s.length()).orElse(0));      // 24
System.out.println(emptySummary.map(s -> s.length()).orElse(0)); // 0

Optional<Integer> length = summary.map(String::length);
System.out.println(length);                                      // Optional[24]

Тип меняется: из Optional<String> получился Optional<Integer>. И ещё одна важная деталь: если функция вернёт null, map() не упадёт, а отдаст пустой Optional — внутри используется ofNullable().

filter(Predicate)

filter() оставляет значение, если оно проходит проверку, и делает контейнер пустым, если не проходит:

Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> shortSummary = Optional.of("Java");

System.out.println(summary.filter(s -> s.length() > 10).orElse("Short summary"));
// Optional methods in Java

System.out.println(shortSummary.filter(s -> s.length() > 10).orElse("Short summary"));
// Short summary

На пустом Optional предикат не вызывается — результат сразу пустой.

flatMap(Function)

flatMap() нужен, когда функция сама возвращает Optional. С обычным map() в таком случае получилась бы двойная обёртка Optional<Optional<T>>, из которой потом неудобно доставать значение:

// getAddress() возвращает Optional<Address>
Optional<Optional<Address>> wrapped = user.map(User::getAddress);   // двойная обёртка
Optional<Address> address = user.flatMap(User::getAddress);         // то, что нужно

Именно на flatMap() держатся цепочки вложенных вызовов, которые раньше писались лесенкой из проверок на null:

String city = repository.findById(42)     // Optional<User>
        .flatMap(User::getAddress)        // Optional<Address>, метод возвращает Optional
        .map(Address::getCity)            // Optional<String>, метод возвращает String
        .filter(c -> !c.isBlank())
        .orElse("город не указан");

Правило выбора простое: если метод возвращает обычный объект — map(); если метод уже возвращает OptionalflatMap().

6. or() и stream()

Оба метода появились в Java 9 и закрывают сценарии, для которых раньше приходилось писать вспомогательный код.

or(Supplier<Optional>)

or() отличается от orElseGet() тем, что возвращает не значение, а Optional — поэтому вызовы можно ставить один за другим:

Optional<String> empty = Optional.empty();

System.out.println(empty.or(() -> Optional.of("fallback")));               // Optional[fallback]
System.out.println(Optional.of("value").or(() -> Optional.of("fallback"))); // Optional[value]

Типичное применение — цепочка источников: взять настройку из кеша, если там пусто — из файла, если и там пусто — из переменных окружения:

String mode = readFromCache()             // Optional<String>
        .or(this::readFromFile)           // Optional<String>
        .or(this::readFromEnvironment)    // Optional<String>
        .orElse("default");

Как и orElseGet(), or() ленив: следующий источник не читается, если предыдущий уже дал значение.

stream()

stream() превращает контейнер в поток из одного элемента — или в пустой поток, если значения нет. Пользы в одиночном вызове немного, но в связке с flatMap() метод убирает целый шаблон:

List<Optional<String>> results = List.of(
        Optional.of("Java"), Optional.empty(), Optional.of("Optional"));

List<String> values = results.stream()
        .flatMap(Optional::stream)
        .toList();                        // [Java, Optional]

// до Java 9 то же самое писали в два шага:
List<String> old = results.stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());

7. OptionalInt, OptionalLong, OptionalDouble

Для примитивов существуют отдельные классы — OptionalInt, OptionalLong и OptionalDouble. Они избавляют от упаковки в объекты-обёртки и возвращаются из методов примитивных потоков (average(), max(), findFirst()).

OptionalInt optionalInt = OptionalInt.of(1);
System.out.println(optionalInt);                   // OptionalInt[1]
System.out.println(optionalInt.getAsInt());        // 1
System.out.println(OptionalInt.empty().orElse(0)); // 0

OptionalDouble average = IntStream.of(28, 4, 91, 30).average();
System.out.println(average);                       // OptionalDouble[38.25]
System.out.println(average.getAsDouble());         // 38.25

OptionalLong optionalLong = OptionalLong.of(10L);
optionalLong.ifPresent(value -> System.out.println(value)); // 10

Вместо get() здесь getAsInt(), getAsLong() и getAsDouble() — они возвращают примитив. Набор методов беднее, чем у Optional<T>: есть isPresent(), isEmpty(), ifPresent(), ifPresentOrElse(), orElse(), orElseGet(), orElseThrow() и stream(), но нет map(), filter(), flatMap() и or(). Если нужны преобразования, перейдите к объектной версии: OptionalInt.of(1).stream().boxed().findFirst() вернёт Optional<Integer>, у которого эти методы есть.

8. equals(), hashCode() и toString()

Optional переопределяет все три метода Object. equals() сравнивает не ссылки на контейнеры, а их содержимое:

System.out.println(Optional.of("Java").equals(Optional.of("Java"))); // true
System.out.println(Optional.empty().equals(Optional.empty()));       // true
System.out.println(Optional.of("Java").equals(Optional.empty()));    // false
System.out.println(Optional.of("Java").equals("Java"));              // false

Последняя строка важна: контейнер никогда не равен своему содержимому. hashCode() возвращает хеш значения, а для пустого контейнера — 0.

toString() даёт удобное для отладки представление, но не годится для вывода пользователю:

System.out.println(Optional.of("Java"));  // Optional[Java]
System.out.println(Optional.empty());     // Optional.empty

Если в консоли или в логе появилось Optional[38.25] вместо числа, значит где-то забыли распаковать контейнер через orElse(), orElseThrow() или getAsDouble().

9. Неочевидные моменты

  1. Аргумент orElse() вычисляется всегда. Даже когда значение в контейнере есть. Для дорогих вычислений и методов с побочными эффектами используйте orElseGet().
  2. get() без проверки — замаскированный NullPointerException. Вместо него берите orElse(), orElseGet() или orElseThrow() со своим сообщением.
  3. Optional как поле класса и как параметр метода — антипаттерн. Класс задумывался как тип возвращаемого значения метода, который может ничего не найти. В полях он добавляет лишний объект на каждый экземпляр и ломает сериализацию: Optional не реализует Serializable. В параметрах он вынуждает вызывающий код писать Optional.of(x) и допускает третье состояние — null вместо самого Optional. Для параметров используйте перегрузку методов, для полей — обычное поле и геттер, возвращающий Optional.
  4. Связка isPresent() + get() ничем не лучше проверки на null. Она просто длиннее. Идиоматичный код обходится ifPresent(), map(), filter() и orElse().
  5. map() проглатывает null. Если функция внутри map() вернула null, исключения не будет — получится пустой Optional. Иногда это удобно, но бывает и источником незаметно потерянных значений.
  6. Не храните Optional в коллекциях. List<Optional<String>> — почти всегда признак того, что пустые элементы стоило отбросить раньше, например через flatMap(Optional::stream).
  7. Optional не защищает от null сам по себе. Переменная типа Optional<String> тоже может быть null, если кто-то присвоил ей null. Метод, возвращающий Optional, обязан возвращать Optional.empty(), а не null.

Совет

Хороший признак того, что Optional используется по назначению: контейнер создаётся внутри метода, живёт несколько строк и исчезает на orElse() или ifPresent(). Если Optional уходит в поле объекта, в параметр конструктора или в коллекцию — почти наверняка проще вернуться к обычной ссылке и проверке.

Часто задаваемые вопросы

Чем orElse отличается от orElseGet?

Результат у них одинаковый, разное время вычисления запасного значения. orElse принимает готовый объект, и это выражение вычисляется всегда, даже если Optional не пуст и результат будет отброшен. orElseGet принимает Supplier и вызывает его только тогда, когда контейнер пуст. Если справа стоит литерал или константа, разницы нет; если там вызов метода, запрос в базу или создание объекта, выбирайте orElseGet - иначе программа делает ненужную работу и может выполнить побочный эффект вроде записи в лог.

Почему get() бросает NoSuchElementException и чем его заменить?

Пустой Optional не содержит значения, поэтому get() нечего вернуть, и он бросает NoSuchElementException с сообщением No value present. Начиная с Java 10 предпочтительной альтернативой в javadoc названа orElseThrow - тот же результат, но по имени метода видно, что вызов может завершиться исключением. Если падать нельзя, используйте orElse, orElseGet или orElseThrow с собственным исключением.

Можно ли использовать Optional как поле класса или параметр метода?

Не рекомендуется. Optional спроектирован как тип возвращаемого значения метода, который может ничего не найти. В поле он добавляет лишний объект на каждый экземпляр и ломает сериализацию, потому что Optional не реализует Serializable. В параметре он заставляет вызывающий код оборачивать аргумент и допускает третье состояние, когда в параметр передали null вместо самого Optional. Для необязательных параметров используйте перегрузку методов, а для полей храните обычную ссылку и возвращайте Optional из геттера.

Чем map отличается от flatMap в Optional?

map применяет функцию к значению и сам оборачивает результат в Optional. flatMap применяет функцию, которая уже возвращает Optional, и не добавляет вторую обёртку. Если вызвать map для метода, возвращающего Optional, получится Optional внутри Optional, из которого неудобно доставать значение. Правило простое: метод возвращает обычный объект - map, метод возвращает Optional - flatMap.

Как проверить, что Optional пуст, не используя отрицание isPresent?

С Java 11 есть метод isEmpty, который возвращает true для пустого контейнера: вместо условия с отрицанием перед isPresent пишется условие с вызовом isEmpty. Если нужно выполнить действие и для значения, и для его отсутствия, с Java 9 доступен ifPresentOrElse: первый аргумент типа Consumer получает значение, второй типа Runnable выполняется, когда значения нет.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.