就像java.util.Optional<T>
在Java 8中(某种程度上)等同于Scala的Option[T]
类型一样,是否也等同于Scala的类型Either[L, R]
?
Answers:
Either
Java 8没有类型,因此您需要自己创建一个或使用一些第三方库。
您可以使用新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
是考虑null
可能的值,所以它的严格“要么”,向左或向右,即使值是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; };
}
}
null
只需Objects.requireNonNull(value)
在两个工厂方法的开头插入,就很容易将其更改为严格拒绝。同样,可以想象增加对空容器的支持。
Either
,但从某种意义上讲类型还是“太大”,因为您left
和right
字段原则上都可以为空或都可以定义。您已经隐藏了使之成为可能的构造函数,但是该方法仍然在实现中留下了潜在的错误。用简单的类型算术术语,您正试图a + b
摆脱(1 + a) * (1 + b)
。当然,a + b
会在该表达式的结果中发生,而1
and也是如此a * b
。
int
因为使用变量int
时会使用整个值范围,这int
是一个例外。毕竟,Optional
这样做是一样的,在对象构造过程中强制不变量。
Either.left(42).map(left -> null, right -> right)
抛出NoSuchElementException
(正确)this.right.get()
(不正确)。同样,可以绕过不变式的执行,Either<empty, empty>
并由产生Either.left(42).mapLeft(left -> null)
。或放在一起时,再次失败Either.left(42).mapLeft(left -> null).map(left -> left, right -> right)
。
Optional.map
允许该函数返回null
,而是将其变为空Optional
。但是,除了有机会发现并立即抛出之外,我看不到任何“更正确”的替代解决方案。Afaik,没有参考行为,例如在Scala中,您无法映射到null
…
Right
代码路径也完全针对Left
实例执行。是的,我更愿意立即失败,而不是先接收<empty, empty>
失败然后再失败。但同样,这全都是口味/风格的问题。
参见Atlassian Fugue。那里有一个很好的实现Either
。
Java标准库中没有任何一个。但是,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的可作为任一或tuple2行动。
不,没有。
Java语言开发人员明确指出类型,如Option<T>
旨在仅作为临时值(例如,在流操作的结果),因此,尽管他们是同样的事情,在其他语言中,他们不应该被使用,因为它们在其他使用语言。因此,不存在这样的事情就不足为奇了,Either
因为它不会像自然地那样自然发生(例如,通过流操作)Optional
。
Either
,自然而然地出现了。也许我做错了。当一个方法可以返回两个不同的东西时,您该怎么办?喜欢Either<List<String>, SomeOtherClass>
吗?
Either
在小型库“矛盾”中有一个独立的实现: http //github.com/poetix/ambivalence
您可以从Maven Central获得它:
<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())