新的Java 8流框架及其朋友可以编写一些非常简洁的Java代码,但我遇到过一个看似简单的情况,但要做到简洁却很棘手。
考虑一个列表<Thing> things and method Optional<Other> resolve(Thing Thing)。我想映射的东西可选<其他>s,并获得第一个其他。
显而易见的解决方案是使用things.stream().flatMap(this::resolve). findfirst(),但flatMap要求您返回一个流,而Optional没有stream()方法(或者它是一个集合或提供一个方法将其转换为或将其视为一个集合)。
我能想到的最好的是:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
但对于一个很普通的例子来说,这似乎太啰嗦了。
有人有更好的主意吗?
我想推广用于为函数式api创建helper的工厂方法:
Optional<R> result = things.stream()
.flatMap(streamopt(this::resolve))
.findFirst();
工厂方法:
<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);
}
推理:
与一般的方法引用相比,与lambda表达式相比,你不能意外地从可访问范围中捕获变量,例如:
T -> streamopt(resolve(o))
它是可组合的,你可以调用Function::and然后在工厂方法result上:
streamopt(::解决).andThen(…)
而在lambda的情况下,你需要先强制转换:
((T <函数,流< R > >) T - > streamopt(解决(T))) .andThen(…)
由于我之前的答案似乎不太受欢迎,我将再试一次。
简短的回答:
你基本上是在正确的轨道上。得到你想要的输出的最短代码是这样的:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst()
.flatMap( Function.identity() );
这将符合您的所有要求:
它将找到解析为非空可选<Result>的第一个响应
它会根据需要惰性地调用::resolve
这个::resolve不会在第一个非空结果之后被调用
它将返回Optional<Result>
再回答
与OP初始版本相比,唯一的修改是我在调用. findfirst()之前删除了.map(可选::get),并添加了. flatmap (o -> o)作为链中的最后一个调用。
当流找到一个实际的结果时,这有一个很好的效果,可以消除double-Optional。
在Java中没有比这个更短的了。
使用更传统的for循环技术的替代代码片段将有相同的代码行数,并且有或多或少相同的顺序和你需要执行的操作数量:
调用this.resolve,
基于Optional.isPresent进行过滤
返回结果和
处理消极结果的方法(当什么都没有发现时)
为了证明我的解决方案像宣传的那样有效,我写了一个小测试程序:
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();
}
}
}
(它确实有一些额外的行来调试和验证,只有尽可能多的调用来解决需要的…)
在命令行上执行这个命令,我得到了以下结果:
$ 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
Java 9
可选的。流已添加到JDK 9。这使你能够做以下事情,而不需要任何帮助方法:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Optional::stream)
.findFirst();
Java 8
是的,这是API中的一个小洞,因为将可选的<T>转换为0或1长度的流<T>有点不方便。你可以这样做:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.findFirst();
不过,在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();
在这里,我内联了对resolve()的调用,而不是使用单独的map()操作,但这只是个人喜好的问题。