就像Java 8中的java.util.Optional<T>
类型有点类似于Scala的Option[T]
类型一样,是否有一个相当于Scala的Either[L, R]
类型的等效物呢?
Java 8 中没有 Either
类型,因此您需要自己创建一个或使用一些第三方库。
您可以使用新的 Optional
类型构建这样的功能(但请阅读本答案的末尾):
final class Either<L,R>
{
public static <L,R> Either<L,R> left(L value) {
return new Either<>(Optional.of(value), Optional.empty());
}
public static <L,R> Either<L,R> right(R value) {
return new Either<>(Optional.empty(), Optional.of(value));
}
private final Optional<L> left;
private final Optional<R> right;
private Either(Optional<L> l, Optional<R> r) {
left=l;
right=r;
}
public <T> T map(
Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc)
{
return left.<T>map(lFunc).orElseGet(()->right.map(rFunc).get());
}
public <T> Either<T,R> mapLeft(Function<? super L, ? extends T> lFunc)
{
return new Either<>(left.map(lFunc),right);
}
public <T> Either<L,T> mapRight(Function<? super R, ? extends T> rFunc)
{
return new Either<>(left, right.map(rFunc));
}
public void apply(Consumer<? super L> lFunc, Consumer<? super R> rFunc)
{
left.ifPresent(lFunc);
right.ifPresent(rFunc);
}
}
示例用例:
new Random().ints(20, 0, 2).mapToObj(i -> (Either<String,Integer>)(i==0?
Either.left("left value (String)"):
Either.right(42)))
.forEach(either->either.apply(
left ->{ System.out.println("received left value: "+left.substring(11));},
right->{ System.out.println("received right value: 0x"+Integer.toHexString(right));}
));
Optional
的解决方案更像是一个学术示例,而不是推荐的方法。其中一个问题是将null
视为“空”,这与“either”的含义相矛盾。Either
,它将null
视为可能的值,因此严格地说是“either”,左或右,即使该值为null
:abstract class Either<L,R>
{
public static <L,R> Either<L,R> left(L value) {
return new Either<L,R>() {
@Override public <T> T map(Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc) {
return lFunc.apply(value);
}
};
}
public static <L,R> Either<L,R> right(R value) {
return new Either<L,R>() {
@Override public <T> T map(Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc) {
return rFunc.apply(value);
}
};
}
private Either() {}
public abstract <T> T map(
Function<? super L, ? extends T> lFunc, Function<? super R, ? extends T> rFunc);
public <T> Either<T,R> mapLeft(Function<? super L, ? extends T> lFunc) {
return this.<Either<T,R>>map(t -> left(lFunc.apply(t)), t -> (Either<T,R>)this);
}
public <T> Either<L,T> mapRight(Function<? super R, ? extends T> lFunc) {
return this.<Either<L,T>>map(t -> (Either<L,T>)this, t -> right(lFunc.apply(t)));
}
public void apply(Consumer<? super L> lFunc, Consumer<? super R> rFunc) {
map(consume(lFunc), consume(rFunc));
}
private <T> Function<T,Void> consume(Consumer<T> c) {
return t -> { c.accept(t); return null; };
}
}
如果在工厂方法的开头插入Objects.requireNonNull(value)
,就可以轻松将其更改为严格拒绝null
。同样地,支持空either也是可以想象的。
int
作为变量的用例发明一个新的“有效范围”类型,因为在使用int
变量时使用整个值范围是例外,只是举个例子。毕竟,Optional
也是这样做的,强制在对象构造期间执行不变式。 - HolgerEither.left(42).map(left -> null, right -> right)
在 this.right.get()
上抛出了 NoSuchElementException
(正确)。此外,可以通过 Either.left(42).mapLeft(left -> null)
来绕过不变量的执行,并产生 Either<empty, empty>
。或者当它们组合在一起时,在 Either.left(42).mapLeft(left -> null).map(left -> left, right -> right)
上再次失败。 - charlieOptional.map
允许函数返回null
,从而将其转换为空的Optional
。然而,除了及时检测并立即抛出异常的机会外,我没有看到任何更“正确”的替代方案。据我所知,与Scala不同,Java中没有参考行为,您不能映射到null
... - HolgerEither
且Scala没有null
,因此没有明确定义的行为。一旦为该情况指定了行为,将该代码适应到该行为上是很容易的。 - Holger撰写本文时,vavr(以前称为javaslang)可能是最受欢迎的Java 8函数库。它与我的另一个答案中所述的lambda-companion的Either非常相似。
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
Java标准库中没有Either。但是FunctionalJava中有Either的实现,以及许多其他好用的类。
cyclops-react有一种“向右”偏置的实现,称为Xor。
Xor.primary("hello")
.map(s->s+" world")
//Primary["hello world"]
Xor.secondary("hello")
.map(s->s+" world")
//Secondary["hello"]
Xor.secondary("hello")
.swap()
.map(s->s+" world")
//Primary["hello world"]
Xor.accumulateSecondary(ListX.of(Xor.secondary("failed1"),
Xor.secondary("failed2"),
Xor.primary("success")),
Semigroups.stringConcat)
//failed1failed2
还有一种相关类型Ior,可以作为either或tuple2。
Xor
在Cyclops X中已被更名为Either
: https://static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/control/Either.html - seanfOption<T>
这样的类型只应被用作临时值(例如在流操作结果中),因此虽然它们与其他语言中的相同,但它们不应该像其他语言中那样使用。因此,并没有类似于Either
的东西,因为它不会像Optional
那样自然地出现(例如从流操作中)。Either<List<String>, SomeOtherClass>
,你会怎么做?请问。 - tamas.kenezOptional
,使其更加适合我的需求:https://dev59.com/XV8e5IYBdhLWcg3wCWx2#76435293 - chaotic3quilibrium有一个独立实现的Either
库叫做"ambivalence": http://github.com/poetix/ambivalence
你可以从Maven中央仓库获取它:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
lambda-companion包含一个 Either
类型(以及其他一些函数类型,例如Try
)。
<dependency>
<groupId>no.finn.lambda</groupId>
<artifactId>lambda-companion</artifactId>
<version>0.25</version>
</dependency>
使用它很容易:
final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())
注意:有关下文所示的Either
类的增强和完整文档版本(包括equals
,hashCode
,flatMap
和其他杂项辅助函数),请访问此Gist。
我采用了@Holger在他的(目前最高票)答案中提供的实现,并稍作修改以消除我能找到的所有null
问题。
我对其进行了重构,使其更符合OpenJDK的命名约定。
然后我阅读了评论,并进行了微调和调整,以进一步提高实现的质量。
我还在构造函数中添加了状态验证,并添加了一些辅助方法:isLeft()
、isRight()
、getLeft()
和getRight()
。
/**
* Implementation of Either<L, R> via a pair of Optionals which explicitly
* reject null values.
* <p>
* Inspired by the (first) solution presented in this StackOverflow Answer:
* <a href="https://dev59.com/XV8e5IYBdhLWcg3wCWx2#26164155">...</a>
**/
public static final class Either<L, R> {
public static <L, R> Either<L, R> left(L value) {
return new Either<>(Optional.of(value), Optional.empty());
}
public static <L, R> Either<L, R> right(R value) {
return new Either<>(Optional.empty(), Optional.of(value));
}
private final Optional<L> left;
private final Optional<R> right;
private Either(Optional<L> left, Optional<R> right) {
if (left.isEmpty() == right.isEmpty()) {
throw new IllegalArgumentException(
"left.isEmpty() and right.isEmpty() cannot be equal");
}
this.left = left;
this.right = right;
}
public boolean isLeft() {
return this.left.isPresent();
}
public boolean isRight() {
return this.right.isPresent();
}
public L getLeft() {
return this.left.get();
}
public R getRight() {
return this.right.get();
}
public <T> T map(
Function<? super L, ? extends T> leftFunction,
Function<? super R, ? extends T> rightFunction
) {
return this.left
.<T>map(l -> Objects.requireNonNull(leftFunction.apply(l)))
.orElseGet(() ->
this.right
.map(r -> Objects.requireNonNull(rightFunction.apply(r)))
.orElseThrow(() ->
new IllegalStateException(
"should never get here")));
}
public <T> Either<T, R> mapLeft(
Function<? super L, ? extends T> leftFunction
) {
return new Either<>(
this.left.map(l ->
Objects.requireNonNull(leftFunction.apply(l))),
this.right);
}
public <T> Either<L, T> mapRight(
Function<? super R, ? extends T> rightFunction
) {
return new Either<>(
this.left,
this.right.map(r ->
Objects.requireNonNull(rightFunction.apply(r))));
}
public void forEach(
Consumer<? super L> leftAction,
Consumer<? super R> rightAction
) {
this.left.ifPresent(leftAction);
this.right.ifPresent(rightAction);
}
}
注意:如果你想获取增强版并完整文档化的Either
类(包括equals
、hashCode
、flatMap
以及其他杂项辅助函数),请访问此处的Gist。
java.lang.Object
... 令人难过。 - Alex Rjava.utils.Either
。但是没有。所以,我就尽量创建了一个类似于我在Scala中喜欢的东西:https://stackoverflow.com/a/76435293/501113 - undefined