Новая потоковая среда Java 8 и ее друзья создают очень лаконичный Java-код, но я столкнулся с на первый взгляд простой ситуацией, которую сложно сделать лаконично.
Рассмотрим List<Thing> things
и метод Optional<Other> resolve(Thing thing)
. Я хочу отобразить Thing
s на Optional<Other>
s и получить первое Other
. Очевидным решением будет использование things.stream().flatMap(this::resolve).findFirst()
, но оно flatMap
требует, чтобы вы возвращали поток, и у Optional
него нет stream()
метода (или он является Collection
или предоставляет метод для его преобразования или просмотра как Collection
).
Лучшее, что я могу придумать, это:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Но это кажется очень скучным для того, что кажется очень распространенным случаем. У кого-нибудь есть идея получше?
java
lambda
java-8
java-stream
Йона Апплетри
источник
источник
.flatMap(Optional::toStream)
, с вашей версией, вы на самом деле видите, что происходит.Optional.stream
существует в JDK 9 сейчас ....Ответы:
Java 9
Optional.stream
был добавлен в JDK 9. Это позволяет вам делать следующее без использования какого-либо вспомогательного метода:Java 8
Да, это была небольшая дыра в API, поскольку неудобно превращать ее
Optional<T>
в ноль или единицу длиныStream<T>
. Вы могли бы сделать это:flatMap
Тем не менее, наличие тернарного оператора внутри является немного громоздким, поэтому для этого может быть лучше написать небольшую вспомогательную функцию:Здесь я добавил вызов
resolve()
вместо того, чтобы выполнять отдельнуюmap()
операцию, но это дело вкуса.источник
static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
Optional
перегрузкуStream#flatMap
... чтобы вы могли просто написатьstream().flatMap(this::resolve)
Optional.stream()
.Я добавляю этот второй ответ на основе предложенного редактирования пользователем srborlongan к моему другому ответу . Я думаю, что предложенная техника была интересной, но она не очень подходила для редактирования моего ответа. Другие согласились, и предложенное редактирование было отклонено. (Я не был одним из избирателей.) Однако у техники есть свои достоинства. Было бы лучше, если бы srborlongan разместил свой ответ. Этого еще не произошло, и я не хотел, чтобы техника терялась в тумане отклоненной истории редактирования StackOverflow, поэтому я решил представить ее как отдельный ответ.
В основном, техника заключается
Optional
в умном использовании некоторых методов, чтобы избежать необходимости использования троичного оператора (? :
) или оператора if / else.Мой встроенный пример будет переписан так:
Мой пример, который использует вспомогательный метод, будет переписан следующим образом:
КОММЕНТАРИЙ
Давайте сравним оригинальную и модифицированную версии напрямую:
Оригинал - простой, если рабочий подход: мы получаем
Optional<Other>
; если оно имеет значение, мы возвращаем поток, содержащий это значение, а если оно не имеет значения, мы возвращаем пустой поток. Довольно просто и легко объяснить.Модификация умна и имеет то преимущество, что избегает условных выражений. (Я знаю, что некоторым людям не нравится троичный оператор. При неправильном использовании он действительно может затруднить понимание кода.) Однако иногда вещи могут быть слишком умными. Измененный код также начинается с
Optional<Other>
. Затем он вызывает,Optional.map
который определяется следующим образом:map(Stream::of)
Вызов возвращаетOptional<Stream<Other>>
. Если значение присутствовало во входном необязательном элементе, возвращаемый необязательный элемент содержит поток, содержащий единственный результат Other. Но если значение не присутствовало, результатом является пустой Необязательный.Далее вызов to
orElseGet(Stream::empty)
возвращает значение типаStream<Other>
. Если его входное значение присутствует, оно получает значение, которое является единственным элементомStream<Other>
. В противном случае (если входное значение отсутствует) возвращается пустое значениеStream<Other>
. Таким образом, результат правильный, такой же, как исходный условный код.В комментариях, обсуждающих мой ответ относительно отклоненного редактирования, я описал эту технику как «более краткую, но и более неясную». Я поддерживаю это. Мне потребовалось некоторое время, чтобы понять, что он делает, и мне также понадобилось некоторое время, чтобы написать приведенное выше описание того, что он делал. Ключевой тонкостью является преобразование из
Optional<Other>
вOptional<Stream<Other>>
. Как только вы поймете это, это имеет смысл, но это не было очевидно для меня.Я признаю, однако, что вещи, которые изначально неясны, могут со временем стать идиоматическими. Может случиться так, что эта техника окажется лучшим на практике, по крайней мере, до тех пор, пока не
Optional.stream
будет добавлена (если она вообще будет).ОБНОВЛЕНИЕ:
Optional.stream
было добавлено в JDK 9.источник
Вы не можете сделать это более кратким, как вы уже делаете.
Вы утверждаете, что не хотите
.filter(Optional::isPresent)
и.map(Optional::get)
.Это было решено методом, описанным @StuartMarks, однако в результате вы теперь отображаете его на a
Optional<T>
, так что теперь вам нужно использовать.flatMap(this::streamopt)
и aget()
в конце.Таким образом, он по-прежнему состоит из двух операторов, и теперь вы можете получить исключения с помощью нового метода! Потому что, что если каждый необязательный пустой? Тогда
findFirst()
вернется пустой необязательный и вашget()
провал!Итак, что у вас есть:
на самом деле это лучший способ выполнить то, что вы хотите, и вы хотите сохранить результат как
T
, а не какOptional<T>
.Я позволил себе создать
CustomOptional<T>
класс, который упаковываетOptional<T>
и предоставляет дополнительный методflatStream()
. Обратите внимание, что вы не можете расширитьOptional<T>
:Вы увидите, что я добавил
flatStream()
, как здесь:Используется в качестве:
Вам все еще нужно будет вернуть
Stream<T>
здесь, так как вы не можете вернутьсяT
, потому что если!optional.isPresent()
, тогда,T == null
если вы объявите это так, но тогда вы.flatMap(CustomOptional::flatStream)
попытаетесь добавитьnull
в поток, и это невозможно.Как пример:
Используется в качестве:
Теперь будет бросать
NullPointerException
внутри потока операций.Вывод
Метод, который вы использовали, на самом деле лучший метод.
источник
Немного более короткая версия с использованием
reduce
:Вы также можете переместить функцию Reduce в метод статической утилиты, и тогда она станет такой:
источник
Поскольку мой предыдущий ответ оказался не очень популярным, я попробую еще раз.
Краткий ответ:
Вы в основном на правильном пути. Самый короткий код, чтобы получить желаемый результат, который я мог придумать, это:
Это будет соответствовать всем вашим требованиям:
Optional<Result>
this::resolve
лениво по мере необходимостиthis::resolve
не будет вызван после первого непустого результатаOptional<Result>
Более длинный ответ
Единственная модификация по сравнению с начальной версией OP состояла в том, что я удалил
.map(Optional::get)
перед вызовом.findFirst()
и добавил.flatMap(o -> o)
как последний вызов в цепочке.Это имеет хороший эффект избавления от двойного Optional, когда поток находит реальный результат.
Вы не можете быть немного короче, чем это в Java.
Альтернативный фрагмент кода, использующий более традиционный
for
метод цикла, будет примерно одинаковым числом строк кода и будет иметь более или менее одинаковый порядок и количество операций, которые необходимо выполнить:this.resolve
,Optional.isPresent
Чтобы доказать, что мое решение работает так, как рекламируется, я написал небольшую тестовую программу:
(У него есть несколько дополнительных строк для отладки и проверки того, что нужно разрешить столько вызовов, сколько необходимо ...)
Выполнив это в командной строке, я получил следующие результаты:
источник
Если вы не против использовать стороннюю библиотеку, вы можете использовать Javaslang . Это похоже на Scala, но реализовано на Java.
Он поставляется с полной неизменной коллекционной библиотекой, которая очень похожа на ту, что известна из Scala. Эти коллекции заменяют коллекции Java и поток Java 8. Он также имеет собственную реализацию Option.
Вот решение для примера исходного вопроса:
Отказ от ответственности: я создатель Javaslang.
источник
Поздно на вечеринку, но как насчет
Вы можете избавиться от последнего метода get (), если создадите метод util для преобразования необязательного потока в поток вручную:
Если вы сразу же вернете поток из функции разрешения, вы сохраните еще одну строку.
источник
Я хотел бы продвинуть фабричные методы для создания помощников для функциональных API:
Заводской метод:
Обоснование:
Как и в случае со ссылками на методы в целом, по сравнению с лямбда-выражениями вы не можете случайно захватить переменную из доступной области, например:
t -> streamopt(resolve(o))
Это составно, например, вы можете вызвать
Function::andThen
метод фабрики:streamopt(this::resolve).andThen(...)
Принимая во внимание, что в случае с лямбдой, вам нужно сначала разыграть его:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
источник
Null поддерживается потоком, предоставленным My library AbacusUtil . Вот код:
источник
Если вы застряли с Java 8, но имеете доступ к Guava 21.0 или новее, вы можете использовать
Streams.stream
для преобразования необязательного в поток.Таким образом, учитывая
ты можешь написать
источник
Что об этом?
https://stackoverflow.com/a/58281000/3477539
источник
return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))
, так же, как вопрос (и ваш связанный ответ) имеет ....get()
безisPresent()
, то вы получите предупреждение в IntelliJСкорее всего, вы делаете это неправильно.
Java 8 Optional не предназначена для использования таким образом. Обычно он зарезервирован только для операций терминального потока, которые могут возвращать или не возвращать значение, например, например, для поиска.
В вашем случае может быть лучше сначала попытаться найти дешевый способ отфильтровать те элементы, которые разрешимы, а затем получить первый элемент в качестве необязательного и разрешить его как последнюю операцию. Еще лучше - вместо фильтрации найдите первый разрешаемый элемент и разрешите его.
Основное правило заключается в том, что вы должны стремиться уменьшить количество элементов в потоке, прежде чем преобразовывать их во что-то другое. YMMV конечно.
источник
Optional<Result> searchFor(Term t)
. Это, кажется, соответствует намерению Факультативного. Кроме того, stream () следует оценивать лениво, поэтому не нужно выполнять дополнительные термины по разрешению после первого совпадения.