Обновлённый удобный Optional в Java 9
Программисту на заметку
16400
0

Класс java.util.Optional появился в языке, начиная с Java 8. В Java 9 он получил обновлённый API. Предлагаем ознакомиться с различными возможностями этого класса на конкретных примерах, в том числе связанных с обработкой NullPointerException (NPE).
Любой Java-программист знает, что едва ли не самой частой ошибкой в программах на этом языке является попытка вызвать метод по нулевой ссылке на объект (NPE). Также разработчики часто сталкиваются с необходимостью получения данных из разных источников с учетом их приоритета. Для решения и той, и другой задачи отлично подходит класс java.util.Optional. Вот о нем и пойдет речь в нашей статье.
Чтобы лучше понять, насколько упростилась жизнь java-программиста с появлением Optional, рассмотрим на конкретном примере, как выполнялась проверка на null до выхода Java 8 и сейчас. Представьте, что у вас есть такие классы:
public class BAZ {
private String code;
public BAZ(Integer suffix) {
this.code = "BAZ_" + suffix;
}
@Override
public String toString() {…}
public String getCode() {return code;}
}
public class BAR {
private String code;
private BAZ baz;
public BAR(String code, Integer id) {
this.code = code;
if (id > 50) {
this.baz = new BAZ(id * 2);
}
}
@Override
public String toString() {…}
public String getCode() {return code;}
public BAZ getBaz() {return baz;}
}
public class FOO {
private static final Random RANDOM = new Random();
private String code;
private List<BAR> bars;
public FOO(String code) {
this.code = code;
this.bars = RANDOM.ints(RANDOM.nextInt(5), 0, 100)
.mapToObj(i -> i % 2 > 0 ? new BAR("CODE_" + i, i) : null)
.collect(Collectors.toList());
}
@Override
public String toString() {…}
public String getCode() {return code;}
public List getBars() {return bars;}
}
Предположим, вы хотите получить code из baz у каждого BAR в списке bars у любого FOO. Чтобы все прошло гладко, придется использовать пресловутую проверку на null:
@Test
public void oldStyle() {
for (FOO foo : foos) {
List bars = foo.getBars();
if (bars == null) {
System.out.println("FOO 'bars' is null => nothing to do");
} else {
for (BAR bar : bars) {
if (bar == null) {
System.out.println("BAR is null => no BUZ indeed");
} else {
BAZ baz = bar.getBaz();
if (baz == null) {
System.out.println("BAR is not null, but BUZ is");
} else {
System.out.println(baz.getCode());
}
}
}
}
}
}
Как вы наверняка знаете, в среднестатистическом проекте таких проверок может быть сотни и даже больше. Недаром в Intellij IDEA есть встроенный шаблон ifn для генерации проверки на null (рис. 1).
Рис. 1. Шаблон ifn для генерации проверки на null в Intellij IDEA
А вот так вы сможете оптимизировать этот код с помощью класса Optional, реализованного в Java 8:
@Test
public void shinyNew8Style() {
foosWithNotOptionalsFields.stream()
.map(FOO::getBars).map(Optional::ofNullable)
.flatMap(bars -> bars.map(Stream::of).orElseGet(Stream::empty))
.flatMap(Collection::stream)
.map(Optional::ofNullable).map(bar -> bar.map(BAR::getBaz))
.map(baz -> baz.map(BAZ::getCode))
.forEach(code -> code.ifPresent(System.out::println));
}
Возьмем еще один пример. Допустим, приложение использует два сервиса, основной One и запасной Two. Вам нужно получить сущность, и, если по какой-то причине это не удалось (сбой связи, ошибка БД и др.), выдать ошибку для ее дальнейшей обработки.
Код с использованием Optional будет выглядеть следующим образом:
@Test
public void optionalEntity8Style() { BAR bar = BAR.getBARFromServiceOne(ID) .orElse(BAR.getBARFromServiceTwo(ID).orElseThrow(ERROR_SUPPLIER)); }
В Java 9 в класс Optional добавлено три новых метода: stream(), ifPresentsOrElse() и or(). Рассмотрим их более подробно.
Optional::stream. Данный метод, как бы странно это не звучало, позволяет преобразовывать Optional в Stream. Если Optional::isPresent, то полученный Stream будет содержать Optional::get, в противном случае Stream будет пустым. Упростим пример с проверкой на null:
@Test
public void shinyNew9Style() {
foosWithNotOptionalsFields.stream()
.flatMap(foo -> Optional.ofNullable(foo.getBars()).stream())
.flatMap(Collection::stream).map(Optional::ofNullable)
.flatMap(Optional::stream)
.flatMap(bar -> Optional.ofNullable(bar.getBaz()).stream())
.forEach(baz -> System.out.println(baz.getCode())); }
Здесь каждый Stream, полученный из Optional, уже содержит отфильтрованные элементы, и необходимость в проверке на null отпадает вовсе! Вызов bar.getBaz() в этих строчках абсолютно безопасен, даже если baz и был .empty, преобразование к Stream позаботится о том, чтобы в потоке не было значений null.
Optional::or. Позволяет легко создавать цепочки вызовов методов, так как возвращает Optional. Для примера воспользуюсь методом получения сущностей из внешних сервисов, только добавлю еще парочку для наглядности (ради интереса попробуйте реализовать такую «цепочку» на Java 8):
@Test
public void optionalEntity9Style() {
BAR bar = BAR.getBARFromServiceOne(ID)
.or(() -> BAR.getBARFromServiceTwo(ID))
.or(() -> BAR.getBARFromServiceThree(ID))
.or(() -> BAR.getBARFromServiceFour(ID))
.orElseThrow(ERROR_SUPPLIER); }
Optional::ifPresentOrElse. Метод позволяет определить последовательность действий в зависимости от того, присутствует ли заданное значение в Optional.
@Test
public void optionalEntityIfPresentOrElse() {
BAR.getBARFromServiceOne(ID)
.or(() -> BAR.getBARFromServiceTwo(ID))
.or(() -> BAR.getBARFromServiceThree(ID))
.or(() -> BAR.getBARFromServiceFour(ID))
.ifPresentOrElse(System.out::println,
() -> System.out.println("Не удалось создать BAR"));
}
Начиная с Java 10 разработчики заметят еще один новый метод класса Optional — orElseThrow(). По сути, он делает то же самое, что и get(), — возвращает значение из Optional, если таковое существует либо генерирует исключение NoSuchElementException. Тем не менее, авторы языка рекомендуют использовать именно orElseThrow() вместо метода get(), который, по их мнению, является опасным благодаря своей сигнатуре и нивелирует все преимущества применения класса.
Использование Optional в коде при работе с объектами, наличие которых определяется во время исполнения, гораздо удобнее проверок с использованием if()…else(). В то же время применять Optional в качестве типа поля класса — плохая идея, не для того этот класс создавали. Как заметил Brian Goetz (Senior Java Language Architect в Oracle, работает над развитием языка и платформы Java), основным намерением при добавлении этой возможности в язык было предоставить механизм для описания возвращаемых значений библиотечных методов в тех случаях, когда нужен способ показать состояние «отсутствия результата», не прибегая к null.
Проверка на null с Optional и без
Чтобы лучше понять, насколько упростилась жизнь java-программиста с появлением Optional, рассмотрим на конкретном примере, как выполнялась проверка на null до выхода Java 8 и сейчас. Представьте, что у вас есть такие классы:
public class BAZ {
private String code;
public BAZ(Integer suffix) {
this.code = "BAZ_" + suffix;
}
@Override
public String toString() {…}
public String getCode() {return code;}
}
public class BAR {
private String code;
private BAZ baz;
public BAR(String code, Integer id) {
this.code = code;
if (id > 50) {
this.baz = new BAZ(id * 2);
}
}
@Override
public String toString() {…}
public String getCode() {return code;}
public BAZ getBaz() {return baz;}
}
public class FOO {
private static final Random RANDOM = new Random();
private String code;
private List<BAR>
public FOO(String code) {
this.code = code;
this.bars = RANDOM.ints(RANDOM.nextInt(5), 0, 100)
.mapToObj(i -> i % 2 > 0 ? new BAR("CODE_" + i, i) : null)
.collect(Collectors.toList());
}
@Override
public String toString() {…}
public String getCode() {return code;}
public List
}
Предположим, вы хотите получить code из baz у каждого BAR в списке bars у любого FOO. Чтобы все прошло гладко, придется использовать пресловутую проверку на null:
@Test
public void oldStyle() {
for (FOO foo : foos) {
List
if (bars == null) {
System.out.println("FOO 'bars' is null => nothing to do");
} else {
for (BAR bar : bars) {
if (bar == null) {
System.out.println("BAR is null => no BUZ indeed");
} else {
BAZ baz = bar.getBaz();
if (baz == null) {
System.out.println("BAR is not null, but BUZ is");
} else {
System.out.println(baz.getCode());
}
}
}
}
}
}
Как вы наверняка знаете, в среднестатистическом проекте таких проверок может быть сотни и даже больше. Недаром в Intellij IDEA есть встроенный шаблон ifn для генерации проверки на null (рис. 1).

Рис. 1. Шаблон ifn для генерации проверки на null в Intellij IDEA
А вот так вы сможете оптимизировать этот код с помощью класса Optional, реализованного в Java 8:
@Test
public void shinyNew8Style() {
foosWithNotOptionalsFields.stream()
.map(FOO::getBars).map(Optional::ofNullable)
.flatMap(bars -> bars.map(Stream::of).orElseGet(Stream::empty))
.map(Optional::ofNullable).map(bar -> bar.map(BAR::getBaz))
.map(baz -> baz.map(BAZ::getCode))
.forEach(code -> code.ifPresent(System.out::println));
}
Возьмем еще один пример. Допустим, приложение использует два сервиса, основной One и запасной Two. Вам нужно получить сущность, и, если по какой-то причине это не удалось (сбой связи, ошибка БД и др.), выдать ошибку для ее дальнейшей обработки.
Код с использованием Optional будет выглядеть следующим образом:
@Test
public void optionalEntity8Style() { BAR bar = BAR.getBARFromServiceOne(ID) .orElse(BAR.getBARFromServiceTwo(ID).orElseThrow(ERROR_SUPPLIER)); }
Optional в Java 9: новые методы API
В Java 9 в класс Optional добавлено три новых метода: stream(), ifPresentsOrElse() и or(). Рассмотрим их более подробно.
Optional::stream. Данный метод, как бы странно это не звучало, позволяет преобразовывать Optional в Stream. Если Optional::isPresent, то полученный Stream будет содержать Optional::get, в противном случае Stream будет пустым. Упростим пример с проверкой на null:
@Test
public void shinyNew9Style() {
foosWithNotOptionalsFields.stream()
.flatMap(foo -> Optional.ofNullable(foo.getBars()).stream())
.flatMap(Collection::stream).map(Optional::ofNullable)
.flatMap(Optional::stream)
.flatMap(bar -> Optional.ofNullable(bar.getBaz()).stream())
.forEach(baz -> System.out.println(baz.getCode())); }
Здесь каждый Stream, полученный из Optional, уже содержит отфильтрованные элементы, и необходимость в проверке на null отпадает вовсе! Вызов bar.getBaz() в этих строчках абсолютно безопасен, даже если baz и был .empty, преобразование к Stream позаботится о том, чтобы в потоке не было значений null.
Optional::or. Позволяет легко создавать цепочки вызовов методов, так как возвращает Optional. Для примера воспользуюсь методом получения сущностей из внешних сервисов, только добавлю еще парочку для наглядности (ради интереса попробуйте реализовать такую «цепочку» на Java 8):
@Test
public void optionalEntity9Style() {
BAR bar = BAR.getBARFromServiceOne(ID)
.or(() -> BAR.getBARFromServiceTwo(ID))
.or(() -> BAR.getBARFromServiceThree(ID))
.or(() -> BAR.getBARFromServiceFour(ID))
.orElseThrow(ERROR_SUPPLIER); }
Optional::ifPresentOrElse. Метод позволяет определить последовательность действий в зависимости от того, присутствует ли заданное значение в Optional.
@Test
public void optionalEntityIfPresentOrElse() {
BAR.getBARFromServiceOne(ID)
.or(() -> BAR.getBARFromServiceTwo(ID))
.or(() -> BAR.getBARFromServiceThree(ID))
.or(() -> BAR.getBARFromServiceFour(ID))
.ifPresentOrElse(System.out::println,
() -> System.out.println("Не удалось создать BAR"));
}
Начиная с Java 10 разработчики заметят еще один новый метод класса Optional — orElseThrow(). По сути, он делает то же самое, что и get(), — возвращает значение из Optional, если таковое существует либо генерирует исключение NoSuchElementException. Тем не менее, авторы языка рекомендуют использовать именно orElseThrow() вместо метода get(), который, по их мнению, является опасным благодаря своей сигнатуре и нивелирует все преимущества применения класса.
Использование Optional в коде при работе с объектами, наличие которых определяется во время исполнения, гораздо удобнее проверок с использованием if()…else(). В то же время применять Optional в качестве типа поля класса — плохая идея, не для того этот класс создавали. Как заметил Brian Goetz (Senior Java Language Architect в Oracle, работает над развитием языка и платформы Java), основным намерением при добавлении этой возможности в язык было предоставить механизм для описания возвращаемых значений библиотечных методов в тех случаях, когда нужен способ показать состояние «отсутствия результата», не прибегая к null.
Читайте также

Зачем Java стал модульным?
Комментарии
Добавить комментарий
Подписка на рассылку
Сортировать
Выберите интересующий Вас продукт компании
Любой продукт
RS-Dealing
Учётное ядро RS-Bank v. 5.5
RSDH: Отчётность ЦБ
Учётное ядро RS-Core V.6
Asseco AML: система для противодействия легализации доходов
InterBank Corporate: ДБО юридических лиц
InterBank Start: ДБО юридических лиц (для развивающихся банков)
RSDH: Управленческая отчётность
Обслуживание физических лиц RS-Retail v. 5.5
RSDH: Финансовое планирование и контроль исполнения бюджета
Обслуживание физических лиц RS-Retail V.6
Asseco InAct : система для предотвращения мошенничества
InterBank Retail: ДБО физических лиц
InterBank FrontOffice: Автоматизация фронт-офисной деятельности
RSDH: Управление клиентскими данными
InterBank Mobile Retail
Автоматизация кредитного бизнеса RS-Loans v. 5.5
InterBank Mobile: Мобильный банк
Расчётная деятельность банка RS-Banking V.6
InterBank Factoring: Автоматизация факторинговых сделок
RSDH: Отчётность МСФО
Работа с ценными бумагами и производными финансовыми инструментами RS-Securities V.6
Платформа InterBank RS
Автоматизация внутрихозяйственной деятельности банка RS-Incounting v. 5.5
RSDH: Портфельная отчётность
RSDH: Система оценки финансового состояния
Выпуск отчётности RS-Reporting V.6
Платформа RS-DataHouse
Межбанковское кредитование RS-Dealing V.6
Кредитование и депозиты RS-Loans V.6
Модуль получения информации из ФНС о блокировках на счетах клиентов
Модуль «RS-Connect. Валидация СНИЛС»
Модуль обмена с ФинЦЕРТ
Проверка паспорта на действительность
Модуль «RS-Connect. Получение выписок из ЕГРЮЛ/ЕГРИП»»
Модуль обмена с СБП
Цифровой профиль
Модуль «RS-Connect. Получение выписки из ПФР»
Сведения об активах и пассивах клиента
шдрщшр
Модуль «RS-Connect. Получение справки 2-НДФЛ»
Модуль «RS-Connect. Проверка действительности паспортов»
Модуль обмена с ЕСИА
RS-Bot: Виртуальный консультант
Модуль «RS-Connect. Работа с реестром банкротов»
Модуль обмена с ЕБС
RS-Digital: Push Server – универсальный push-сервер
Модуль «RS-Connect. Работа с реестром МСП»
Модуль обмена с ФССП
RS-Digital Front Office
Модуль «RS-Connect. Упрощенная идентификация»
Модуль обмена с ЦИК
Модуль обмена с ПФР
RS-Payments
Модуль «RS-Connect. Цифровой профиль»
Модуль обмена с ЭББГ
Отчетность в формате XBRL
Модуль обмена с ФТС
Модуль получения результатов госуслуг
RS-Insurance Front Office
Получение ИНН
Модуль обмена с Росреестром
Работа со справочником ПДЛ
RS-Insurance: XBRL
Модуль обмена с ГИС ЖКХ
Обмен с ПФР информацией по материнскому (семейному) капиталу
Business Universe RS
Работа с реестром МСП
RSDH: Система контроля качества данных
Asseco Live: CRM платформа с интегрированным контакт-центром
Модуль обмена с ФНС
Проект импортозамещения в ИТ
Модуль получения выписок из ЕГРЮЛ/ЕГРИП
Подключение к СПФС
Работа с реестром банкротов
Модуль обмена с ФСФМ
Оперативная организация обмена данными с СПФС Банка России
Модуль упрощенной идентификации
Система RS-FinMarkets
Модуль обмена с ГИС ГМП
Модуль валидации СНИЛС
Модуль упрощенной идентификации
Обмен с платформой ЗСК
Модуль обмена с ФНП
Обмен с платформой ЗСК
