Java8 将多个可选参数映射到一个函数中
Java 8 Mapping multiple optional parameters into a function
假设我有函数 Object f(String a, String b)
并且我想调用两个不同的函数 return 可选字符串来获取 f Optional<String> getA()
和 Optional<String> getB()
。我可以想到两种解决方案,但看起来都不那么干净,尤其是当您有更多参数时:
1:
return getA().flatMap(
a -> getB().map(
b -> f(a,b)).get()
2:
Optional<String> a = getA();
Optional<String> b = getB();
if(a.isPresent() && b.isPresent()) {
return f(a.get(), b.get());
}
有更简洁的方法吗?
您可以流式传输参数并仅应用一次条件,但这是否比您的解决方案更优雅,则在旁观者的眼中:
if (Stream.of(a, b).allMatch(Optional::isPresent)) {
return f(a.get(), b.get());
}
您刚刚在函数式编程中偶然发现了一个名为 lifting 的概念,它使您能够 lift 常规函数(例如 A -> B
) 到新域(例如 Optional<A> -> Optional<B>
)。
在 Haskell 和类似的语言中,还有一种用于 flatMapping 和映射的语法糖更舒适地称为 do notation,而 for comprehension 在 Scala 中。它为您提供了一种保持流程线性并避免嵌套的方法(您在示例 1 中被迫经历)。
Java,不幸的是没有这样的东西,因为它的函数式编程能力很差,甚至 Optional
也不是真正的第一个 class 公民(没有标准 API 实际使用它)。
所以你坚持使用你已经发现的方法。
如果您对上述概念感到好奇,请继续阅读。
提升
假设你有:
public String f(A a, B b) {
return b + "-" + a;
}
与其等效的 Scala:
def f(a: A, b: B) = b + "-" + a
将 f
提升到 Option
(与 Java 中的 Optional
相同)看起来像这样(使用 Scalaz 库,参见 for Cats)
val lifted = Monad[Option].lift2(f)
lifted
现在是一个等效于:
的函数
public Optional<String> f(Optional<A> a, Optional<B> b) {
if(a.isPresent() && b.isPresent()) {
return Optional.of(b + "-" + a);
}
return Optional.empty;
}
正是您要查找的内容,在 1 行中,适用于任何上下文(例如 List
,而不仅仅是 Option
)和任何函数。
为了理解/不要表示法
使用理解,你的例子看起来像这样(我认为,我的Scala很弱):
for {
a <- getA();
b <- getB();
} yield f(a, b)
同样,这适用于任何可以平面映射的东西,例如 List
、Future
等
如果您确定 a 和 b 都存在(正如您在解决方案 1 中对 get
的最终调用似乎暗示的那样),我认为这非常简单:
return f(getA().orElseThrow(() -> new NoSuchElementException("a not present")),
getB().orElseThrow(() -> new NoSuchElementException("b not present")));
如果您不确定两者是否都存在,我更喜欢您的解决方案 1。它利用 Optional
是最好的。只有我不会在最后调用 get
,而是调用 orElse
或者在你的情况下有意义的,例如:
return getA()
.flatMap(a -> getB().map(b -> f(a,b)))
.orElse("Not both present");
我认为,如果没有好的使用方法Optional
,那么无论如何也没有理由尝试使用它。
我发现这比您的选项 2 更简洁:
String a = getA().orElse(null);
String b = getB().orElse(null);
if(a != null && b != null) {
return f(a, b);
}
假设我有函数 Object f(String a, String b)
并且我想调用两个不同的函数 return 可选字符串来获取 f Optional<String> getA()
和 Optional<String> getB()
。我可以想到两种解决方案,但看起来都不那么干净,尤其是当您有更多参数时:
1:
return getA().flatMap(
a -> getB().map(
b -> f(a,b)).get()
2:
Optional<String> a = getA();
Optional<String> b = getB();
if(a.isPresent() && b.isPresent()) {
return f(a.get(), b.get());
}
有更简洁的方法吗?
您可以流式传输参数并仅应用一次条件,但这是否比您的解决方案更优雅,则在旁观者的眼中:
if (Stream.of(a, b).allMatch(Optional::isPresent)) {
return f(a.get(), b.get());
}
您刚刚在函数式编程中偶然发现了一个名为 lifting 的概念,它使您能够 lift 常规函数(例如 A -> B
) 到新域(例如 Optional<A> -> Optional<B>
)。
在 Haskell 和类似的语言中,还有一种用于 flatMapping 和映射的语法糖更舒适地称为 do notation,而 for comprehension 在 Scala 中。它为您提供了一种保持流程线性并避免嵌套的方法(您在示例 1 中被迫经历)。
Java,不幸的是没有这样的东西,因为它的函数式编程能力很差,甚至 Optional
也不是真正的第一个 class 公民(没有标准 API 实际使用它)。
所以你坚持使用你已经发现的方法。
如果您对上述概念感到好奇,请继续阅读。
提升
假设你有:
public String f(A a, B b) {
return b + "-" + a;
}
与其等效的 Scala:
def f(a: A, b: B) = b + "-" + a
将 f
提升到 Option
(与 Java 中的 Optional
相同)看起来像这样(使用 Scalaz 库,参见
val lifted = Monad[Option].lift2(f)
lifted
现在是一个等效于:
public Optional<String> f(Optional<A> a, Optional<B> b) {
if(a.isPresent() && b.isPresent()) {
return Optional.of(b + "-" + a);
}
return Optional.empty;
}
正是您要查找的内容,在 1 行中,适用于任何上下文(例如 List
,而不仅仅是 Option
)和任何函数。
为了理解/不要表示法
使用理解,你的例子看起来像这样(我认为,我的Scala很弱):
for {
a <- getA();
b <- getB();
} yield f(a, b)
同样,这适用于任何可以平面映射的东西,例如 List
、Future
等
如果您确定 a 和 b 都存在(正如您在解决方案 1 中对 get
的最终调用似乎暗示的那样),我认为这非常简单:
return f(getA().orElseThrow(() -> new NoSuchElementException("a not present")),
getB().orElseThrow(() -> new NoSuchElementException("b not present")));
如果您不确定两者是否都存在,我更喜欢您的解决方案 1。它利用 Optional
是最好的。只有我不会在最后调用 get
,而是调用 orElse
或者在你的情况下有意义的,例如:
return getA()
.flatMap(a -> getB().map(b -> f(a,b)))
.orElse("Not both present");
我认为,如果没有好的使用方法Optional
,那么无论如何也没有理由尝试使用它。
我发现这比您的选项 2 更简洁:
String a = getA().orElse(null);
String b = getB().orElse(null);
if(a != null && b != null) {
return f(a, b);
}