Методы класса Optional в Java - Вопросы
Всего: 6 вопросов
1. Чем orElse() отличается от orElseGet() в Optional?
Чем orElse() отличается от orElseGet() в Optional?
Результат у них одинаковый, разное время вычисления запасного значения. orElse(T other) принимает готовый объект: аргумент — обычное выражение, которое вычисляется всегда, ещё до входа в метод, даже если Optional не пуст и результат будет отброшен. orElseGet(Supplier<T>) принимает функцию и вызывает её лениво, только когда контейнер пуст. Пока справа стоит литерал или константа, разницы нет: orElse(""), orElse(0), orElse(List.of()). Как только там появляется вызов метода — запрос в базу, чтение файла, создание тяжёлого объекта или метод с побочным эффектом (запись в лог, увеличение счётчика) — эта работа выполнится зря и побочный эффект произойдёт даже для непустого Optional. Практическое правило: orElse() — для готовых значений, orElseGet() — для всего, что нужно вычислять.
2. Почему get() бросает NoSuchElementException и чем его заменить?
Почему 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 и что они возвращают?
Что делают методы 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 и зачем они нужны?
Какие методы появились у 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>?
Чем 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 задумывался как тип возвращаемого значения метода, который может ничего не найти. В поле он добавляет лишний объект на каждый экземпляр и ломает сериализацию: Optional не реализует Serializable. В параметре он вынуждает вызывающий код писать Optional.of(x) и допускает третье состояние — в параметр можно передать null вместо самого контейнера, и проверка на пустоту не спасёт. Для необязательных параметров используйте перегрузку методов, для состояния — обычное поле и геттер, возвращающий Optional. По той же причине не стоит хранить Optional в коллекциях: List<Optional<String>> почти всегда означает, что пустые элементы стоило отбросить раньше, например через flatMap(Optional::stream). Здоровый признак: контейнер создан внутри метода, живёт несколько строк и исчезает на orElse() или ifPresent().