在 Stream::flatMap 中使用 Java 8 的 Optional
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22725537/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Java 8's Optional with Stream::flatMap
提问by Yona Appletree
The new Java 8 stream framework and friends make for some very concise java code, but I have come across a seemingly-simple situation that is tricky to do concisely.
新的 Java 8 流框架和朋友创造了一些非常简洁的 Java 代码,但我遇到了一个看似简单的情况,但很难做到简洁。
Consider a List<Thing> things
and method Optional<Other> resolve(Thing thing)
. I want to map the Thing
s to Optional<Other>
s and get the first Other
. The obvious solution would be to use things.stream().flatMap(this::resolve).findFirst()
, but flatMap
requires that you return a stream, and Optional
doesn't have a stream()
method (or is it a Collection
or provide a method to convert it to or view it as a Collection
).
考虑一个List<Thing> things
和方法Optional<Other> resolve(Thing thing)
。我想将Thing
s映射到Optional<Other>
s 并获得第一个Other
. 显而易见的解决方案是使用things.stream().flatMap(this::resolve).findFirst()
,但flatMap
要求您返回一个流,并且Optional
没有stream()
方法(或者它是 aCollection
或提供将其转换为或查看为 a 的方法Collection
)。
The best I can come up with is this:
我能想到的最好的是:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
But that seems awfully long-winded for what seems like a very common case. Anyone have a better idea?
但这对于看似非常普遍的案例来说似乎非常冗长。有人有更好的主意吗?
采纳答案by Stuart Marks
Java 9
爪哇 9
Optional.stream
has been added to JDK 9. This enables you to do the following, without the need of any helper method:
Optional.stream
已添加到 JDK 9。这使您可以执行以下操作,而无需任何辅助方法:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Optional::stream)
.findFirst();
Java 8
爪哇 8
Yes, this was a small hole in the API, in that it's somewhat inconvenient to turn an Optional into a zero-or-one length Stream. You could do this:
是的,这是 API 中的一个小漏洞,因为将 Optional 转换为长度为零或一的 Stream 有点不方便。你可以这样做:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.findFirst();
Having the ternary operator inside the flatMap is a bit cumbersome, though, so it might be better to write a little helper function to do this:
但是,在 flatMap 中使用三元运算符有点麻烦,因此最好编写一个小辅助函数来执行此操作:
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
if (opt.isPresent())
return Stream.of(opt.get());
else
return Stream.empty();
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
Here, I've inlined the call to resolve() instead of having a separate map() operation, but this is a matter of taste.
在这里,我内联了对 resolve() 的调用,而不是单独的 map() 操作,但这是一个品味问题。
回答by Roland Tepp
Most likely You are doing it wrong.
很可能你做错了。
Java 8 Optional is not meant to be used in this manner. It is usually only reserved for terminal stream operations that may or may not return a value, like find for example.
Java 8 Optional 不打算以这种方式使用。它通常只保留用于可能返回或可能不返回值的终端流操作,例如 find 。
In your case it might be better to first try to find a cheap way to filter out those items that are resolvable and then get the first item as an optional and resolve it as a last operation. Better yet - instead of filtering, find the first resolvable item and resolve it.
在您的情况下,最好首先尝试找到一种廉价的方法来过滤掉那些可解析的项目,然后将第一个项目作为可选项目并将其作为最后一个操作进行解析。更好的是 - 而不是过滤,找到第一个可解析的项目并解决它。
things.filter(Thing::isResolvable)
.findFirst()
.flatMap(this::resolve)
.get();
Rule of thumb is that you should strive to reduce number of items in the stream before you transform them to something else. YMMV of course.
经验法则是,在将流中的项目转换为其他项目之前,您应该努力减少它们的数量。当然是YMMV。
回答by skiwi
You cannot do it more concise as you are already doing.
你不能做得更简洁,因为你已经在做。
You claim that you do not want .filter(Optional::isPresent)
and.map(Optional::get)
.
你声称你不想要.filter(Optional::isPresent)
和.map(Optional::get)
。
This has been resolved by the method @StuartMarks describes, however as a result you now map it to an Optional<T>
, so now you need to use .flatMap(this::streamopt)
and a get()
in the end.
这已经通过@StuartMarks 描述的方法解决了,但是结果你现在将它映射到 an Optional<T>
,所以现在你需要使用.flatMap(this::streamopt)
和 aget()
最后。
So it still consists of two statements and you can now get exceptions with the new method! Because, what if every optional is empty? Then the findFirst()
will return an empty optional and your get()
will fail!
所以它仍然由两个语句组成,您现在可以使用新方法获得异常!因为,如果每个可选项都是空的呢?然后findFirst()
将返回一个空的可选项,您get()
将失败!
So what you have:
所以你有什么:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
isactually the best way to accomplish what you want, and that is you want to save the result as a T
, not as an Optional<T>
.
是真正实现你想要什么是最好的办法,那就是你要的结果保存为一个T
,而不是一个Optional<T>
。
I took the liberty of creating a CustomOptional<T>
class that wraps the Optional<T>
and provides an extra method, flatStream()
. Note that you cannot extend Optional<T>
:
我冒昧地创建了一个CustomOptional<T>
类来包装Optional<T>
并提供一个额外的方法flatStream()
. 请注意,您不能扩展Optional<T>
:
class CustomOptional<T> {
private final Optional<T> optional;
private CustomOptional() {
this.optional = Optional.empty();
}
private CustomOptional(final T value) {
this.optional = Optional.of(value);
}
private CustomOptional(final Optional<T> optional) {
this.optional = optional;
}
public Optional<T> getOptional() {
return optional;
}
public static <T> CustomOptional<T> empty() {
return new CustomOptional<>();
}
public static <T> CustomOptional<T> of(final T value) {
return new CustomOptional<>(value);
}
public static <T> CustomOptional<T> ofNullable(final T value) {
return (value == null) ? empty() : of(value);
}
public T get() {
return optional.get();
}
public boolean isPresent() {
return optional.isPresent();
}
public void ifPresent(final Consumer<? super T> consumer) {
optional.ifPresent(consumer);
}
public CustomOptional<T> filter(final Predicate<? super T> predicate) {
return new CustomOptional<>(optional.filter(predicate));
}
public <U> CustomOptional<U> map(final Function<? super T, ? extends U> mapper) {
return new CustomOptional<>(optional.map(mapper));
}
public <U> CustomOptional<U> flatMap(final Function<? super T, ? extends CustomOptional<U>> mapper) {
return new CustomOptional<>(optional.flatMap(mapper.andThen(cu -> cu.getOptional())));
}
public T orElse(final T other) {
return optional.orElse(other);
}
public T orElseGet(final Supplier<? extends T> other) {
return optional.orElseGet(other);
}
public <X extends Throwable> T orElseThrow(final Supplier<? extends X> exceptionSuppier) throws X {
return optional.orElseThrow(exceptionSuppier);
}
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
@Override
public boolean equals(final Object obj) {
return optional.equals(obj);
}
@Override
public int hashCode() {
return optional.hashCode();
}
@Override
public String toString() {
return optional.toString();
}
}
You will see that I added flatStream()
, as here:
您会看到我添加了flatStream()
,如下所示:
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
Used as:
用作:
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.flatMap(CustomOptional::flatStream)
.findFirst()
.get();
You stillwill need to return a Stream<T>
here, as you cannot return T
, because if !optional.isPresent()
, then T == null
if you declare it such, but then your .flatMap(CustomOptional::flatStream)
would attempt to add null
to a stream and that is not possible.
您仍然需要在Stream<T>
此处返回 a ,因为您无法返回T
,因为如果!optional.isPresent()
,那么T == null
如果您将其声明为这样,但是您.flatMap(CustomOptional::flatStream)
将尝试添加null
到流中,这是不可能的。
As example:
例如:
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
Used as:
用作:
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.map(CustomOptional::getTOrNull)
.findFirst()
.get();
Will now throw a NullPointerException
inside the stream operations.
现在将NullPointerException
在流操作中抛出一个。
Conclusion
结论
The method you used, is actually the best method.
你用的方法,其实是最好的方法。
回答by Stuart Marks
I'm adding this second answer based on a proposed edit by user srborlonganto my other answer. I think the technique proposed was interesting, but it wasn't really suitable as an edit to my answer. Others agreed and the proposed edit was voted down. (I wasn't one of the voters.) The technique has merit, though. It would have been best if srborlongan had posted his/her own answer. This hasn't happened yet, and I didn't want the technique to be lost in the mists of the StackOverflow rejected edit history, so I decided to surface it as a separate answer myself.
我正在根据用户srborlongan的建议编辑将第二个答案添加到我的其他答案中。我认为提出的技术很有趣,但它并不适合作为我的答案的编辑。其他人同意,提议的编辑被否决。(我不是投票者之一。)不过,这项技术有其优点。如果 srborlongan 发布了他/她自己的答案,那就更好了。这还没有发生,我不希望该技术在 StackOverflow 拒绝编辑历史的迷雾中迷失,所以我决定自己将它作为一个单独的答案浮出水面。
Basically the technique is to use some of the Optional
methods in a clever way to avoid having to use a ternary operator (? :
) or an if/else statement.
基本上,该技术是以Optional
巧妙的方式使用某些方法,以避免必须使用三元运算符 ( ? :
) 或 if/else 语句。
My inline example would be rewritten this way:
我的内联示例将以这种方式重写:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
An my example that uses a helper method would be rewritten this way:
我的一个使用辅助方法的示例将被重写为:
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
return opt.map(Stream::of)
.orElseGet(Stream::empty);
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
COMMENTARY
评论
Let's compare the original vs modified versions directly:
让我们直接比较原始版本与修改版本:
// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
The original is a straightforward if workmanlike approach: we get an Optional<Other>
; if it has a value, we return a stream containing that value, and if it has no value, we return an empty stream. Pretty simple and easy to explain.
原始方法是一种简单直接的方法:我们得到一个Optional<Other>
; 如果它有一个值,我们返回一个包含该值的流,如果它没有值,我们返回一个空流。非常简单易懂。
The modification is clever and has the advantage that it avoids conditionals. (I know that some people dislike the ternary operator. If misused it can indeed make code hard to understand.) However, sometimes things can be too clever. The modified code also starts off with an Optional<Other>
. Then it calls Optional.map
which is defined as follows:
修改很聪明,并且具有避免条件的优点。(我知道有些人不喜欢三元运算符。如果误用它确实会使代码难以理解。)但是,有时事情可能太聪明了。修改后的代码也以Optional<Other>
. 然后它调用Optional.map
定义如下:
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.
如果存在值,则对其应用提供的映射函数,如果结果非空,则返回描述结果的 Optional。否则返回一个空的 Optional。
The map(Stream::of)
call returns an Optional<Stream<Other>>
. If a value was present in the input Optional, the returned Optional contains a Stream that contains the single Other result. But if the value was not present, the result is an empty Optional.
该map(Stream::of)
调用返回Optional<Stream<Other>>
。如果输入 Optional 中存在值,则返回的 Optional 包含一个包含单个其他结果的 Stream。但如果该值不存在,则结果是一个空的 Optional。
Next, the call to orElseGet(Stream::empty)
returns a value of type Stream<Other>
. If its input value is present, it gets the value, which is the single-element Stream<Other>
. Otherwise (if the input value is absent) it returns an empty Stream<Other>
. So the result is correct, the same as the original conditional code.
接下来,调用orElseGet(Stream::empty)
返回一个类型的值Stream<Other>
。如果它的输入值存在,它会获取值,即单元素Stream<Other>
。否则(如果输入值不存在)它返回一个空的Stream<Other>
。所以结果是正确的,和原来的条件码一样。
In the comments discussing on my answer, regarding the rejected edit, I had described this technique as "more concise but also more obscure". I stand by this. It took me a while to figure out what it was doing, and it also took me a while to write up the above description of what it was doing. The key subtlety is the transformation from Optional<Other>
to Optional<Stream<Other>>
. Once you grok this it makes sense, but it wasn't obvious to me.
在讨论我的答案的评论中,关于被拒绝的编辑,我将这种技术描述为“更简洁但也更模糊”。我坚持这一点。我花了一段时间才弄清楚它在做什么,我也花了一些时间来写下上面对它在做什么的描述。关键的微妙之处在于从Optional<Other>
到的转换Optional<Stream<Other>>
。一旦你理解了这一点,它就说得通了,但对我来说并不明显。
I'll acknowledge, though, that things that are initially obscure can become idiomatic over time. It might be that this technique ends up being the best way in practice, at least until Optional.stream
gets added (if it ever does).
不过,我承认,最初晦涩难懂的事物随着时间的推移可能会变得习以为常。可能这种技术最终成为实践中的最佳方式,至少在Optional.stream
被添加之前(如果它曾经这样做的话)。
UPDATE:Optional.stream
has been added to JDK 9.
更新:Optional.stream
已添加到 JDK 9。
回答by Andrejs
A slightly shorter version using reduce
:
一个稍短的版本使用reduce
:
things.stream()
.map(this::resolve)
.reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );
You could also move the reduce function to a static utility method and then it becomes:
您还可以将 reduce 函数移动到静态实用程序方法,然后它变成:
.reduce(Optional.empty(), Util::firstPresent );
回答by Daniel Dietrich
If you don't mind to use a third party library you may use Javaslang. It is like Scala, but implemented in Java.
如果您不介意使用第三方库,您可以使用Javaslang。它类似于 Scala,但用 Java 实现。
It comes with a complete immutable collection library that is very similar to that known from Scala. These collections replace Java's collections and Java 8's Stream. It also has its own implementation of Option.
它带有一个完整的不可变集合库,与 Scala 中已知的非常相似。这些集合取代了 Java 的集合和 Java 8 的 Stream。它也有自己的 Option 实现。
import javaslang.collection.Stream;
import javaslang.control.Option;
Stream<Option<String>> options = Stream.of(Option.some("foo"), Option.none(), Option.some("bar"));
// = Stream("foo", "bar")
Stream<String> strings = options.flatMap(o -> o);
Here is a solution for the example of the initial question:
以下是初始问题示例的解决方案:
import javaslang.collection.Stream;
import javaslang.control.Option;
public class Test {
void run() {
// = Stream(Thing(1), Thing(2), Thing(3))
Stream<Thing> things = Stream.of(new Thing(1), new Thing(2), new Thing(3));
// = Some(Other(2))
Option<Other> others = things.flatMap(this::resolve).headOption();
}
Option<Other> resolve(Thing thing) {
Other other = (thing.i % 2 == 0) ? new Other(i + "") : null;
return Option.of(other);
}
}
class Thing {
final int i;
Thing(int i) { this.i = i; }
public String toString() { return "Thing(" + i + ")"; }
}
class Other {
final String s;
Other(String s) { this.s = s; }
public String toString() { return "Other(" + s + ")"; }
}
Disclaimer: I'm the creator of Javaslang.
免责声明:我是 Javaslang 的创建者。
回答by Roland Tepp
As my previous answerappeared not to be very popular, I will give this another go.
由于我之前的答案似乎不是很受欢迎,我将再试一次。
A short answer:
简短的回答:
You are mostly on a right track. The shortest code to get to your desired output I could come up with is this:
你大多在正确的轨道上。我可以想出的获得所需输出的最短代码是:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst()
.flatMap( Function.identity() );
This will fit all your requirements:
这将满足您的所有要求:
- It will find first response that resolves to a nonempty
Optional<Result>
- It calls
this::resolve
lazily as needed this::resolve
will not be called after first non-empty result- It will return
Optional<Result>
- 它将找到解析为非空的第一个响应
Optional<Result>
- 它
this::resolve
根据需要懒惰地调用 this::resolve
在第一个非空结果后不会被调用- 它会回来
Optional<Result>
Longer answer
更长的答案
The only modification compared to OP initial version was that I removed .map(Optional::get)
before call to .findFirst()
and added .flatMap(o -> o)
as the last call in the chain.
与 OP 初始版本相比,唯一的修改是我.map(Optional::get)
在调用之前删除.findFirst()
并添加.flatMap(o -> o)
为链中的最后一个调用。
This has a nice effect of getting rid of the double-Optional, whenever stream finds an actual result.
每当流找到实际结果时,这都有一个很好的摆脱双重可选的效果。
You can't really go any shorter than this in Java.
在 Java 中,你真的不能比这更短。
The alternative snippet of code using the more conventional for
loop technique is going to be about same number of lines of code and have more or less same order and number of operations you need to perform:
使用更传统的for
循环技术的替代代码片段将是大约相同数量的代码行,并且具有或多或少相同的顺序和您需要执行的操作数量:
- Calling
this.resolve
, - filtering based on
Optional.isPresent
- returning the result and
- some way of dealing with negative result (when nothing was found)
- 打电话
this.resolve
, - 基于过滤
Optional.isPresent
- 返回结果和
- 某种处理负面结果的方法(当什么都没有发现时)
Just to prove that my solution works as advertised, I wrote a small test program:
为了证明我的解决方案如宣传的那样有效,我编写了一个小测试程序:
public class StackOverflow {
public static void main( String... args ) {
try {
final int integer = Stream.of( args )
.peek( s -> System.out.println( "Looking at " + s ) )
.map( StackOverflow::resolve )
.filter( Optional::isPresent )
.findFirst()
.flatMap( o -> o )
.orElseThrow( NoSuchElementException::new )
.intValue();
System.out.println( "First integer found is " + integer );
}
catch ( NoSuchElementException e ) {
System.out.println( "No integers provided!" );
}
}
private static Optional<Integer> resolve( String string ) {
try {
return Optional.of( Integer.valueOf( string ) );
}
catch ( NumberFormatException e )
{
System.out.println( '"' + string + '"' + " is not an integer");
return Optional.empty();
}
}
}
(It does have few extra lines for debugging and verifying that only as many calls to resolve as needed...)
(它确实有一些额外的用于调试和验证的行,以便根据需要解析尽可能多的调用......)
Executing this on a command line, I got the following results:
在命令行上执行此操作,我得到以下结果:
$ java StackOferflow a b 3 c 4
Looking at a
"a" is not an integer
Looking at b
"b" is not an integer
Looking at 3
First integer found is 3
回答by user_3380739
Null is supported by the Stream provided My library AbacusUtil. Here is code:
Null 由 Stream 提供的 My library AbacusUtil 支持。这是代码:
Stream.of(things).map(e -> resolve(e).orNull()).skipNull().first();
回答by Ljubopytnov
Late to the party, but what about
聚会迟到了,但是呢
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst().get();
You can get rid of the last get() if you create a util method to convert optional to stream manually:
如果您创建一个 util 方法来手动将 optional 转换为流,则可以摆脱最后一个 get() :
things.stream()
.map(this::resolve)
.flatMap(Util::optionalToStream)
.findFirst();
If you return stream right away from your resolve function, you save one more line.
如果您立即从解析函数返回流,则可以多保存一行。
回答by charlie
I'd like to promote factory methodsfor creating helpers for functional APIs:
我想推广用于为功能 API 创建助手的工厂方法:
Optional<R> result = things.stream()
.flatMap(streamopt(this::resolve))
.findFirst();
The factory method:
工厂方法:
<T, R> Function<T, Stream<R>> streamopt(Function<T, Optional<R>> f) {
return f.andThen(Optional::stream); // or the J8 alternative:
// return t -> f.apply(t).map(Stream::of).orElseGet(Stream::empty);
}
Reasoning:
推理:
As with method references in general, compared to lambda expressions, you can't accidentaly capture a variable from the accessible scope, like:
t -> streamopt(resolve(o))
It's composable, you can e.g. call
Function::andThen
on the factory method result:streamopt(this::resolve).andThen(...)
Whereas in the case of a lambda, you'd need to cast it first:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
与一般的方法引用一样,与 lambda 表达式相比,您不能意外地从可访问范围中捕获变量,例如:
t -> streamopt(resolve(o))
它是可组合的,您可以例如调用
Function::andThen
工厂方法结果:streamopt(this::resolve).andThen(...)
而在 lambda 的情况下,您需要先转换它:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)