Yon*_*ree 221 java lambda java-8 java-stream
新的Java 8流框架和朋友们制作了一些非常简洁的Java代码,但是我遇到了一个看似简单的情况,简单易懂.
考虑一个List<Thing> things方法Optional<Other> resolve(Thing thing).我想将Things 映射到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();
Run Code Online (Sandbox Code Playgroud)
但这似乎是一个非常普遍的案例,似乎非常冗长.谁有更好的主意?
Stu*_*rks 238
Optional.stream 已添加到JDK 9.这使您可以执行以下操作,而无需任何帮助方法:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Optional::stream)
.findFirst();
Run Code Online (Sandbox Code Playgroud)
是的,这是API中的一个小漏洞,因为将Optional变为零或一个长度的流有点不方便.你可以这样做:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.findFirst();
Run Code Online (Sandbox Code Playgroud)
但是在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();
Run Code Online (Sandbox Code Playgroud)
在这里,我已经内联调用resolve()而不是单独的map()操作,但这是一个品味问题.
Stu*_*rks 65
我正在根据用户srborlongan提议的编辑添加第二个答案给我的另一个答案.我认为提出的技术很有意思,但它并不适合作为我的答案的编辑.其他人同意,拟议的编辑被否决.(我不是选民之一.)但这项技术有其优点.如果srborlongan发布了他/她自己的答案,那将是最好的.这还没有发生,我不希望这个技术在StackOverflow拒绝编辑历史的迷雾中丢失,所以我决定将它作为一个单独的答案自己表现出来.
基本上,该技术是以Optional巧妙的方式使用某些方法,以避免必须使用三元运算符(? :)或if/else语句.
我的内联示例将以这种方式重写:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
Run Code Online (Sandbox Code Playgroud)
我使用辅助方法的示例将以这种方式重写:
/**
* 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();
Run Code Online (Sandbox Code Playgroud)
评论
让我们直接比较原始版本和修改版本:
// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
Run Code Online (Sandbox Code Playgroud)
原始是一种直接的,如果工作的方法:我们得到一个Optional<Other>; 如果它有一个值,我们返回一个包含该值的流,如果它没有值,我们返回一个空流.非常简单易懂.
修改是聪明的,并且具有避免条件的优点.(我知道有些人不喜欢三元运算符.如果误用它确实会让代码难以理解.)但是,有时事情可能太聪明了.修改后的代码也以一个开头Optional<Other>.然后调用Optional.map定义如下:
如果存在值,则将提供的映射函数应用于该值,如果结果为非null,则返回描述结果的Optional.否则返回一个空的Optional.
该map(Stream::of)呼叫返回一个Optional<Stream<Other>>.如果输入Optional中存在值,则返回的Optional包含一个包含单个Other结果的Stream.但是如果该值不存在,则结果为空可选.
接下来,调用orElseGet(Stream::empty)返回一个type类型的值Stream<Other>.如果其输入值存在,则获取值,即单个元素Stream<Other>.否则(如果输入值不存在),则返回空Stream<Other>.因此结果是正确的,与原始条件代码相同.
在讨论我的答案的评论中,关于被拒绝的编辑,我把这种技术描述为"更简洁但也更模糊".我支持这个.我花了一段时间才弄清楚它在做什么,我花了一些时间来写出它正在做的事情的上述描述.关键的微妙之处在于从转变Optional<Other>为Optional<Stream<Other>>.一旦你理解了它,这是有道理的,但对我来说并不明显.
不过,我会承认,最初模糊不清的事情随着时间的推移会变得不恰当.可能这种技术最终成为实践中的最佳方式,至少在Optional.stream被添加之前(如果有的话).
更新: Optional.stream已添加到JDK 9.
ski*_*iwi 14
你不能像你已经做的那样更简洁.
你声称自己不想.filter(Optional::isPresent) 和 .map(Optional::get).
这已经通过@StuartMarks描述的方法解决了,但是结果你现在将它映射到一个Optional<T>,所以现在你需要使用.flatMap(this::streamopt)和a get()到底.
因此它仍然包含两个语句,现在您可以使用新方法获得异常!因为,如果每个可选项都是空的怎么办?然后findFirst()将返回一个空的可选项,你的get()意志将失败!
那么你有什么:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
Run Code Online (Sandbox Code Playgroud)
是真正实现你想要什么是最好的办法,那就是你要的结果保存为一个T,而不是一个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();
}
}
Run Code Online (Sandbox Code Playgroud)
你会看到我添加了flatStream(),如下所示:
public Stream<T> flatStream() {
if (!optional.isPresent()) {
return Stream.empty();
}
return Stream.of(get());
}
Run Code Online (Sandbox Code Playgroud)
用作:
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.flatMap(CustomOptional::flatStream)
.findFirst()
.get();
Run Code Online (Sandbox Code Playgroud)
你还需要返回Stream<T>这里,因为你不能返回T,因为如果!optional.isPresent(),那么T == null如果你声明它这样的,但那么你.flatMap(CustomOptional::flatStream)会尝试添加null到流,这是不可能的.
例如:
public T getTOrNull() {
if (!optional.isPresent()) {
return null;
}
return get();
}
Run Code Online (Sandbox Code Playgroud)
用作:
String result = Stream.of("a", "b", "c", "de", "fg", "hij")
.map(this::resolve)
.map(CustomOptional::getTOrNull)
.findFirst()
.get();
Run Code Online (Sandbox Code Playgroud)
现在将抛出一个NullPointerException内部流操作.
您使用的方法实际上是最好的方法.
一个略短的版本使用reduce:
things.stream()
.map(this::resolve)
.reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );
Run Code Online (Sandbox Code Playgroud)
您还可以将reduce函数移动到静态实用程序方法,然后它变为:
.reduce(Optional.empty(), Util::firstPresent );
Run Code Online (Sandbox Code Playgroud)
由于我之前的回答似乎不太受欢迎,因此我将再说一遍。
您大多处于正确的轨道上。我想出的最短的代码可以达到您想要的输出:
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst()
.flatMap( Function.identity() );
Run Code Online (Sandbox Code Playgroud)
这将满足您的所有要求:
Optional<Result>this::resolve懒惰地打电话this::resolve 第一个非空结果后将不会调用Optional<Result>与OP初始版本相比,唯一的修改是我.map(Optional::get)在调用之前删除了该链接,.findFirst()并将其添加.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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
(它的确有很少的额外行可用于调试和验证,仅需要调用的电话数量即可解决...)
在命令行上执行此操作,得到以下结果:
$ 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
Run Code Online (Sandbox Code Playgroud)
小智 5
聚会迟到了,但是怎么样
things.stream()
.map(this::resolve)
.filter(Optional::isPresent)
.findFirst().get();
Run Code Online (Sandbox Code Playgroud)
如果您创建一个 util 方法来手动将可选值转换为流,则可以摆脱最后一个 get() :
things.stream()
.map(this::resolve)
.flatMap(Util::optionalToStream)
.findFirst();
Run Code Online (Sandbox Code Playgroud)
如果您立即从解析函数返回流,则可以多保存一行。
我想推广为功能性 API 创建助手的工厂方法:
Optional<R> result = things.stream()
.flatMap(streamopt(this::resolve))
.findFirst();
Run Code Online (Sandbox Code Playgroud)
工厂方法:
<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);
}
Run Code Online (Sandbox Code Playgroud)
推理:
与一般的方法引用一样,与 lambda 表达式相比,您不能意外地从可访问范围捕获变量,例如:
t -> streamopt(resolve(o))
它是可组合的,您可以例如调用Function::andThen工厂方法结果:
streamopt(this::resolve).andThen(...)
而在 lambda 的情况下,您需要先对其进行强制转换:
((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)
| 归档时间: |
|
| 查看次数: |
127293 次 |
| 最近记录: |