Java 8中有与Scala等效的Either吗?


79

就像java.util.Optional<T>在Java 8中(某种程度上)等同于Scala的Option[T]类型一样,是否也等同于Scala的类型Either[L, R]


在所有这些时间之后仍然没有任何标准的实现方法:(
Cherry

总有java.lang.Object ...伤心。
Alex R

Answers:


68

EitherJava 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)在两个工厂方法的开头插入,就很容易将其更改为严格拒绝。同样,可以想象增加对空容器的支持。


9
请记住,尽管行为类似于Either,但从某种意义上讲类型还是“太大”,因为您leftright字段原则上都可以为空或都可以定义。您已经隐藏了使之成为可能的构造函数,但是该方法仍然在实现中留下了潜在的错误。用简单的类型算术术语,您正试图a + b摆脱(1 + a) * (1 + b)。当然,a + b会在该表达式的结果中发生,而1and也是如此a * b
神秘丹

6
@Mysterious Dan:在对象构造期间禁止某种状态是Java中的首选方法。否则,您将不得不为几乎每个用例发明一个新的“有效范围”类型,int因为使用变量int时会使用整个值范围,这int是一个例外。毕竟,Optional这样做是一样的,在对象构造过程中强制不变量。
Holger 2014年

1
@Holger: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)
查理

2
@charlie:此解决方案不考虑Optional.map允许该函数返回null,而是将其变为空Optional。但是,除了有机会发现并立即抛出之外,我看不到任何“更正确”的替代解决方案。Afaik,没有参考行为,例如在Scala中,您无法映射到null
Holger

1
@Holger:我同意没有“更正确的”方法,我只是不喜欢这样的事实:即使错误相同,Right代码路径也完全针对Left实例执行。是的,我更愿意立即失败,而不是先接收<empty, empty>失败然后再失败。但同样,这全都是口味/风格的问题。
查理

26

在撰写本文时,vavr(以前为javaslang)可能是最受欢迎的功能性Java 8库。在我的另一个答案中,它与lambda-companion的Either非常相似。

Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();



10

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行动。

  • 披露我是cyclops-react的作者。


6

不,没有。

Java语言开发人员明确指出类型,如Option<T>旨在仅作为临时值(例如,在流操作的结果),因此,尽管他们同样的事情,在其他语言中,他们不应该被使用,因为它们在其他使用语言。因此,不存在这样的事情就不足为奇了,Either因为它不会像自然地那样自然发生(例如,通过流操作)Optional


8
您有资料来源吗?
akroy 2015年

3
@akroy,这似乎是正确的,Brian Goetz在此答案中写道:link
RonyHe'9

2
对我来说Either,自然而然地出现了。也许我做错了。当一个方法可以返回两个不同的东西时,您该怎么办?喜欢Either<List<String>, SomeOtherClass>吗?
tamas.kenez

3
我也反对Either自然出现,对我来说,在其中映射操作可能会引发异常的流中,所以我映射到Either <Exception,Result>流
OlivierGérardin19年


5

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())

1
项目不再维护。
Flame_Phoenix
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.