在 Java 8 中链接可选
Chaining Optionals in Java 8
正在寻找一种链接可选值的方法,以便返回第一个存在的选项。如果 none 存在,则应返回 Optional.empty()
。
假设我有几个这样的方法:
Optional<String> find1()
我正在尝试链接它们:
Optional<String> result = find1().orElse( this::find2 ).orElse( this::find3 );
但这当然行不通,因为 orElse
需要一个值,而 orElseGet
需要一个 Supplier
。
你可以这样做:
Optional<String> resultOpt = Optional.of(find1()
.orElseGet(() -> find2()
.orElseGet(() -> find3()
.orElseThrow(() -> new WhatEverException()))));
虽然我不确定它是否提高了 IMO 的可读性。 Guava 提供了一种链接 Optionals 的方法:
import com.google.common.base.Optional;
Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3());
它可能是您问题的另一种选择,但在 JDK.
中不使用标准可选 class
如果你想保持标准API,你可以写一个简单的实用方法:
static <T> Optional<T> or(Optional<T> first, Optional<T> second) {
return first.isPresent() ? first : second;
}
然后:
Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3()));
如果你有很多可选链,也许最好使用 Stream 方法,就像其他已经提到的那样。
使用流:
Stream.of(find1(), find2(), find3())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
如果您需要延迟计算查找方法,请使用供应商函数:
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
可能是
之一
public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) {
if (first.isPresent()) return first;
for (Supplier<Optional <? extends T>> sup : supp) {
Optional<? extends T> opt = sup.get();
if (opt.isPresent()) {
return opt;
}
}
return Optional.empty();
}
public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) {
if (first.isPresent()) return first;
Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent);
return present.findFirst().orElseGet(Optional::empty);
}
会做。
第一个遍历一组供应商。第一个非空 Optional<>
是 returned。如果找不到,我们 return 一个空的 Optional
。
第二个对遍历 Suppliers
的 Stream
做同样的事情,每个人(懒惰地)询问它们的值,然后过滤空的 Optional
s .第一个非空的是 returned,或者如果不存在,则为空的。
受 Sauli 的回答启发,可以使用 flatMap()
方法。
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
将 Optional 转换为 Stream 很麻烦。显然,这将是 fixed with JDK9。所以这可以写成
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(Optional::stream)
.findFirst();
Java9发布后更新
虽然原题是关于Java8的,但是在Java9中引入了Optional::or
,有了它,问题可以如下解决
Optional<String> result = find1()
.or(this::find2)
.or(this::find3);
执行可选链接首先转换流到可选使用两种方法之一
- findAny() 或 findFirst()
- 最小值() / 最大值()
一旦获得可选的,可选的还有两个实例方法,它们也存在于 Stream class 中,即 filter 和 map()。
在方法上使用这些并检查输出使用 ifPresent(System.out :: Println)
例如:
流 s = Stream.of(1,2,3,4);
s.findFirst().filter((a)->a+1).ifPresent(System.out :: Println)
输出为:2
基于 Alexis C 的回答,但没有嵌套 orElse
s
String result = find1()
.map(Optional::of)
.orElseGet(Foo::find2())
.map(Optional::of)
.orElseGet(Foo::find3())
.orElseThrow(() -> new WhatEverException())
如果您想要 Optional<String>
作为结果,请删除 orElseThrow
。
诀窍是在每个 orElseGet
.
之前将 findX
返回的每个可选包装到另一个可选
用于级联链接您可以使用 ifPresentOrElse
find1().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
find2().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
find3().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
System.err.println( "nothing found…" );
}
} );
}
} );
}
} );
要用 Optional
的值做某事你必须用你的 Consumer
替换 System.out::println
(此解决方案中也可能有不同的消费者)
// Java 9+
find1().or(() -> find2()).or(() -> find3());
// Java 8
Optional.ofNullable(
find1().orElse(
find2().orElse(
find3().orElse( null )
)));
我对所有这些问题的通用通用解决方案:
public static <T> T firstMatch(final Predicate<T> matcher, final T orElse, final T... values) {
for (T t : values) {
if (matcher.test(t)) {
return t;
}
}
return orElse;
}
那么你可以这样做:
public static <T> Optional<T> firstPresent(final Optional<T>... values) {
return firstMatch(Optional::isPresent, Optional.empty(), values);
}
自 Java 9
读者正在寻找的最有可能的案例(今天)
result = find1()
.or(this::find2)
.or(this::find3);
Java 8
result = Optional.ofNullable(find1()
.orElse(find2()
.orElse(find3()
.orElse(null))));
正在寻找一种链接可选值的方法,以便返回第一个存在的选项。如果 none 存在,则应返回 Optional.empty()
。
假设我有几个这样的方法:
Optional<String> find1()
我正在尝试链接它们:
Optional<String> result = find1().orElse( this::find2 ).orElse( this::find3 );
但这当然行不通,因为 orElse
需要一个值,而 orElseGet
需要一个 Supplier
。
你可以这样做:
Optional<String> resultOpt = Optional.of(find1()
.orElseGet(() -> find2()
.orElseGet(() -> find3()
.orElseThrow(() -> new WhatEverException()))));
虽然我不确定它是否提高了 IMO 的可读性。 Guava 提供了一种链接 Optionals 的方法:
import com.google.common.base.Optional;
Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3());
它可能是您问题的另一种选择,但在 JDK.
中不使用标准可选 class如果你想保持标准API,你可以写一个简单的实用方法:
static <T> Optional<T> or(Optional<T> first, Optional<T> second) {
return first.isPresent() ? first : second;
}
然后:
Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3()));
如果你有很多可选链,也许最好使用 Stream 方法,就像其他已经提到的那样。
使用流:
Stream.of(find1(), find2(), find3())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
如果您需要延迟计算查找方法,请使用供应商函数:
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
可能是
之一 public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) {
if (first.isPresent()) return first;
for (Supplier<Optional <? extends T>> sup : supp) {
Optional<? extends T> opt = sup.get();
if (opt.isPresent()) {
return opt;
}
}
return Optional.empty();
}
public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) {
if (first.isPresent()) return first;
Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent);
return present.findFirst().orElseGet(Optional::empty);
}
会做。
第一个遍历一组供应商。第一个非空 Optional<>
是 returned。如果找不到,我们 return 一个空的 Optional
。
第二个对遍历 Suppliers
的 Stream
做同样的事情,每个人(懒惰地)询问它们的值,然后过滤空的 Optional
s .第一个非空的是 returned,或者如果不存在,则为空的。
受 Sauli 的回答启发,可以使用 flatMap()
方法。
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
将 Optional 转换为 Stream 很麻烦。显然,这将是 fixed with JDK9。所以这可以写成
Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(Optional::stream)
.findFirst();
Java9发布后更新
虽然原题是关于Java8的,但是在Java9中引入了Optional::or
,有了它,问题可以如下解决
Optional<String> result = find1()
.or(this::find2)
.or(this::find3);
执行可选链接首先转换流到可选使用两种方法之一
- findAny() 或 findFirst()
- 最小值() / 最大值()
一旦获得可选的,可选的还有两个实例方法,它们也存在于 Stream class 中,即 filter 和 map()。 在方法上使用这些并检查输出使用 ifPresent(System.out :: Println)
例如:
流 s = Stream.of(1,2,3,4);
s.findFirst().filter((a)->a+1).ifPresent(System.out :: Println)
输出为:2
基于 Alexis C 的回答,但没有嵌套 orElse
s
String result = find1()
.map(Optional::of)
.orElseGet(Foo::find2())
.map(Optional::of)
.orElseGet(Foo::find3())
.orElseThrow(() -> new WhatEverException())
如果您想要 Optional<String>
作为结果,请删除 orElseThrow
。
诀窍是在每个 orElseGet
.
findX
返回的每个可选包装到另一个可选
用于级联链接您可以使用 ifPresentOrElse
find1().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
find2().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
find3().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
System.err.println( "nothing found…" );
}
} );
}
} );
}
} );
要用 Optional
的值做某事你必须用你的 Consumer
替换 System.out::println
(此解决方案中也可能有不同的消费者)
// Java 9+
find1().or(() -> find2()).or(() -> find3());
// Java 8
Optional.ofNullable(
find1().orElse(
find2().orElse(
find3().orElse( null )
)));
我对所有这些问题的通用通用解决方案:
public static <T> T firstMatch(final Predicate<T> matcher, final T orElse, final T... values) {
for (T t : values) {
if (matcher.test(t)) {
return t;
}
}
return orElse;
}
那么你可以这样做:
public static <T> Optional<T> firstPresent(final Optional<T>... values) {
return firstMatch(Optional::isPresent, Optional.empty(), values);
}
自 Java 9
读者正在寻找的最有可能的案例(今天)
result = find1()
.or(this::find2)
.or(this::find3);
Java 8
result = Optional.ofNullable(find1()
.orElse(find2()
.orElse(find3()
.orElse(null))));