Image:Репка. https://botan.cc/
В серии статей:
Java 8 Optional и объекты с динамической структурой. Часть 1,
Java 8 Optional и объекты с динамической структурой. Часть 2,
Java 8 Optional и объекты с динамической структурой. Часть 3,
Закроем дыру в Java: Класс Result
я описал области применения и достоинства класса Optional появившегося в Java 8, а также некоторые недоработки в реализации этого класса.
В Java 9 некоторые недостатки были устранены путем расширения класса новыми методами. Именно их мы и рассмотрим в этой статье.
Исходные тексты этого всех моих статей на эту тему вы найдете в GitHub: https://github.com/vsirotin/Smartenesse-Java
Всего в класс Optional<T> в Java 9 добавлено три новых метода: stream(), ifPresentsOrElse() и or(). Начнем наше рассмотрение.
Метод stream(): берем все что можно
Этот метод полезен, если у вас имеется список List<Optional<T>>. Каждый элемент списка это чехол, или контейнер, который может содержать “настоящий” элемент либо быть пустым. Если вам необходимо наиболее простым способом получить из этого списка все “настоящие” элементы – вам поможет в этом метод stream().
Представим себе такую ситуацию. Программист Иван работает в проекте в режиме удаленного доступа (remote). Это дало ему возможность переехать жить в домик в деревне. В своем огороде он посадил несколько грядок овощей. Каждое утро Иван выходит в свой огород собирает с каждого куста созревшие плоды. На каждом кусте плод за ночь может созреть, а может и не созреть. Поэтому мы можем смоделировать урожай каждого куста с помощью Optional<T>. Для упрощения мы будем использовать в качестве T класс String.
Таким образом, урожай овощей, собираемый Иваном каждое утро мы можем смоделировать как List<Optional<String>> getTomatoBeds().
Предположим, мы хотим получить список плодов (разумеется созревших) в виде массива.
Без использования stream() нам пришлось бы для этой цели написать с помощью for … цикл, перебрать в нем все “чехлы”, записать “настоящие” элементы в список а оттуда переписать их в массив.
В Java 8 для массив созревших овощей можно получить так:
String[] result = tomatoGarden.getTomatoBeds() .stream() .filter(Optional::isPresent) .map(Optional::get) .toArray(String[]::new);
А в Java 9 это можно сделать ещё короче и элегантнее:
String[] result = tomatoGarden.getTomatoBeds() .stream() .flatMap(Optional::stream) .toArray(String[]::new);
Замена двух строчек на одну оказалась возможной благодаря вызову внутри flatMap нового метода stream(), который описан в документации Oralcle https://docs.oracle.com/javase/9/docs/api/java/util/Optional.html так:
Stream<T>
stream()
If a value is present, returns a sequentialStream
containing only that value, otherwise returns an emptyStream
.
В большинстве случаев мы используем метод stream() для обработки больших последовательностей данных. В случае с Optional<T> мы столкнулись со stream() для обработки последовательности максимум из одного элемента.
Для закрепления ещё один пример:
public void testOptionalStreamBase() { Optional<String> opFilled = Optional.of("Filled"); assertEquals(1, opFilled.stream().count()); Optional<String> opEmpty = Optional.empty(); assertEquals(0, opEmpty.stream().count()); }
Metod ifPresentOrElse(): Если нет – добавим!
Эта новая функция закрыла дыру, оставшуюся в Java 8 и которую приходилось закрывать комбинацией вызовов методов ifPresent(Consumer<? super T> action) и orElse(T other).
Посмотрим снова документацию на новый метод:
void
ifPresentOrElse(Consumer<? superT> action, Runnable emptyAction)
If a value is present, performs the given action with the value, otherwise performs the given empty-based action.
Как видим, он позволяет внутри себя обработать и заполненный и пустой “чехол” (Optional<T>)
Итак продолжим историю с Иваном. Иван каждое утро выходит в свой огород, собирает созревшие овощи и складывает их в салат. Для моделирования этого факта мы будем использовать метод setValue(String s). А вот если на грядках ничего не выросло, ему приходится доставать консервированные овощи из банки. Для этого мы будем использовать метод setDefault().
Вот их реализация:
private void setValue(String s){ veg = s;} private void setDefault(){ veg = CANNED_FOOD;}
Переменная класса veg это то, что окажется у Ивана в салатнице.
А теперь проверим, как ifPresentOrElse() работает с помощью теста:
@Test public void testOptionalStreamIfPresentOrElse() { Optional<String> optFilled = Optional.of(TOMATO); optFilled.ifPresentOrElse(this::setValue, this::setDefault); assertEquals(TOMATO, veg); Optional<String> optEmpty = Optional.empty(); optEmpty.ifPresentOrElse(this::setValue, this::setDefault); assertEquals(CANNED_FOOD, veg); }
Как мы видим, с помощью этой модели Иван хоть и не нашел на грядке свежего овоща, но заместил его овощем из консервной банки.
Метод or(): упорно ищем своё счастье!
Внимательный читатель наверное подметил, что в предыдущем примере Иван обследовал только первый куст. А как смоделировать ситуацию, если кустов много?
В этом случае нам поможет метод or(). Снова заглянем в документацию:
Optional<T>
or(Supplier<? extends Optional<? extends T>> supplier)
If a value is present, returns anOptional
describing the value, otherwise returns anOptional
produced by the supplying function.
Другими словами, с помощью or() можно строить цепочки обработки, которые будут анализировать “чехлы” (Optional<T>) до тех пор, пока не встретится первый непустой элемент.
В следующем примере optBed1, 2, 3 моделируют овощные грядки или отдельные кусты.
@Test public void testOptionalOr1() { Optional<String> optBed1 = Optional.empty(); Optional<String> optBed2 = Optional.of(TOMATO); Optional<String> optBed3 = Optional.of(CUCUMBER); String res = optBed1 .or(()->{return optBed2;}) .or(()->optBed3) .or(this::getDefault) .get(); assertEquals(res, TOMATO); }
Поздравим Ивана, с помощью нового метода or() он в этот раз продвинулся дальше, нашел свежий помидор и ему не придется есть овощи из консервной банки.