Методы класса Optional в Java - Вопросы

Всего: 6 вопросов

1. 

Чем orElse() отличается от orElseGet() в Optional?

Результат у них одинаковый, разное время вычисления запасного значения. orElse(T other) принимает готовый объект: аргумент — обычное выражение, которое вычисляется всегда, ещё до входа в метод, даже если Optional не пуст и результат будет отброшен. orElseGet(Supplier<T>) принимает функцию и вызывает её лениво, только когда контейнер пуст. Пока справа стоит литерал или константа, разницы нет: orElse(""), orElse(0), orElse(List.of()). Как только там появляется вызов метода — запрос в базу, чтение файла, создание тяжёлого объекта или метод с побочным эффектом (запись в лог, увеличение счётчика) — эта работа выполнится зря и побочный эффект произойдёт даже для непустого Optional. Практическое правило: orElse() — для готовых значений, orElseGet() — для всего, что нужно вычислять.

2. 

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

Пустой Optional не содержит значения, поэтому get() нечего вернуть — он бросает java.util.NoSuchElementException с сообщением No value present. Вызов get() без предварительной проверки бессмыслен: он просто превращает NullPointerException в NoSuchElementException. Начиная с Java 10 в javadoc предпочтительной альтернативой назван orElseThrow() без аргументов: делает то же самое, но по имени метода на месте вызова видно, что он может завершиться исключением. Если нужно своё исключение — orElseThrow(() -> new UserNotFoundException("Пользователь не найден")). Если падать нельзя — orElse(), orElseGet(), ifPresent(). Сам get() не помечен как @Deprecated, поэтому компилятор о нём молчит.

3. 

Что делают методы map() и filter() у Optional и что они возвращают?

Оба возвращают не значение, а новый Optional, поэтому складываются в цепочки. map(Function) применяет функцию к значению и заворачивает результат обратно в контейнер, при этом тип может измениться: summary.map(String::length) превращает Optional<String> в Optional<Integer>. filter(Predicate) оставляет значение, если оно проходит проверку, и отдаёт пустой Optional, если не проходит. На пустом контейнере ни функция map(), ни предикат filter() не вызываются вообще — результат сразу пустой, поэтому Optional.empty().map(s -> s.length()).orElse(0) вернёт 0 без исключения. Важная деталь: если функция внутри map() вернёт null, исключения не будет — внутри используется ofNullable(), и получится пустой Optional. Если функция сама возвращает Optional, нужен flatMap(), иначе появится двойная обёртка Optional<Optional<T>>.

4. 

Какие методы появились у Optional после Java 8 и зачем они нужны?

Java 9: ifPresentOrElse(Consumer, Runnable) — действие для значения и отдельное действие для пустого контейнера, вместо if/else вокруг isPresent(); or(Supplier<Optional>) — если пусто, подставляет Optional из другого источника и возвращает именно Optional, поэтому вызовы выстраиваются в цепочку запасных вариантов (кеш, файл, переменные окружения); stream() — превращает контейнер в Stream из нуля или одного элемента, в связке flatMap(Optional::stream) отбрасывает пустые значения в потоке. Java 10: orElseThrow() без аргументов — самодокументированная замена get(). Java 11: isEmpty() — возвращает true для пустого контейнера, читается лучше, чем !optional.isPresent(). Если код должен собираться под Java 8, эти методы использовать нельзя.

5. 

Чем OptionalInt, OptionalLong и OptionalDouble отличаются от Optional<T>?

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

6. 

Почему Optional как поле класса или параметр метода считается антипаттерном?

Optional задумывался как тип возвращаемого значения метода, который может ничего не найти. В поле он добавляет лишний объект на каждый экземпляр и ломает сериализацию: Optional не реализует Serializable. В параметре он вынуждает вызывающий код писать Optional.of(x) и допускает третье состояние — в параметр можно передать null вместо самого контейнера, и проверка на пустоту не спасёт. Для необязательных параметров используйте перегрузку методов, для состояния — обычное поле и геттер, возвращающий Optional. По той же причине не стоит хранить Optional в коллекциях: List<Optional<String>> почти всегда означает, что пустые элементы стоило отбросить раньше, например через flatMap(Optional::stream). Здоровый признак: контейнер создан внутри метода, живёт несколько строк и исчезает на orElse() или ifPresent().

Страница 1 из 1