在Java 8中是否有相当于Scala的Either?

HRJ*_*HRJ 69 java scala java-8

就像java.util.Optional<T>在Java 8中(有点)等同于Scala的Option[T]类型,是否有相当于Scala的Either[L, R]

Hol*_*ger 60

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用例示例:

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));}
));
Run Code Online (Sandbox Code Playgroud)

在回顾中,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; };
    }
}
Run Code Online (Sandbox Code Playgroud)

null只需Objects.requireNonNull(value)在两种工厂方法的开头插入一个,就可以很容易地将其改为严格拒绝.同样,添加对空的支持也是可以想象的.

  • 请记住,虽然这个行为类似于'Either`,但在某种意义上,类型"太大",因为你的`left`和`right`字段原则上都可以是空的或者都是定义的.您已经隐藏了可以实现这一点的构造函数,但该方法仍然存在实现中的错误的可能性.在简单类型的算术术语中,你试图从`(1 + a)*(1 + b)`中得到'a + b`.当然,在该表达式的结果中出现"a + b",但是`1`和`a*b`也是如此. (8认同)
  • @Mysterious Dan:在对象构造期间禁止某种状态是Java中的首选方式.否则,你必须为`int`的几乎每个用例发明一个新的"有效范围"类型,因为当使用`int`变量时,使用整个值范围`int`就是一个例子.毕竟,`Optional`也是如此,在对象构造过程中强制执行不变量. (6认同)
  • @Holger:“Either.left(42).map(left -&gt; null, right -&gt; right)”在“this.right.get()”(不正确)上抛出“NoSuchElementException”(正确)。另外,我们可以绕过不变量的强制执行并通过“Either.left(42).mapLeft(left -&gt; null)”生成“Either&lt;empty,empty&gt;”。或者当放在一起时,“Either.left(42).mapLeft(left -&gt; null).map(left -&gt; left, right -&gt; right)”再次失败。 (2认同)
  • @charlie:这个解决方案不认为`Optional.map`允许函数返回`null`,把它变成一个空的`Optional`.然而,除了有机会检测到并立即投掷之外,我没有看到任何"更正确"的替代解决方案.Afaik,没有引用行为,就像在Scala中一样,你不能映射到`null` ... (2认同)
  • @Holger你可以有“class Left extends Either”和“class Right extends Either”——这是完美的Java风格,因为它是策略模式的使用。 (2认同)
  • @Blaisorblade:这将是一种替代实现,并且不可避免的代码重复将是最少的,所以,是的,这将是一种选择。但对于该类的用户来说,无论如何都没有什么区别。请注意,JRE 实现者决定不创建“EmptyOptional”和“NonEmptyOptional”实现类,而是在内部使用条件。这可能会减少元空间的消耗,但是,我认为技术差异太小,无法称一种方法优于另一种方法。我两年前临时写了这门课,肯定还有其他选择…… (2认同)
  • @Blaisorblade:这里没有提到错误,只有一个不受支持的情况,问题没有给出明确定义的行为,因为只指定了 Scala 的“Either”,而 Scala 没有“null”。一旦该情况有了指定的行为,就可以很容易地使该代码适应该行为。 (2认同)
  • @Blaisorblade:你可以有一个包含“null”的“Either”?这将使“Option[T]”和“Either[L, R]”的整个概念变得毫无用处。 (2认同)

Zhe*_*lov 23

Atlassian Fugue.那里有很好的实施Either.


whi*_*win 22

在撰写本文时,vavr(以前称为javaslang)可能是最受欢迎的Java 8函数库.在我的另一个答案中,它非常类似于lambda-companion的Either.

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


Rav*_*ran 17

Java标准库中没有Either.但是,在FunctionalJava中有一个Either的实现,以及许多其他很好的类.


Joh*_*ean 9

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
Run Code Online (Sandbox Code Playgroud)

还有一个相关的类型Ior的可作为任一或tuple2行动.

  • 披露我是独眼巨人反应的作者.

  • “ Xor”在Cyclops X中被重命名为“ Either”(https://github.com/aol/cyclops-react/issues/749):https://static.javadoc.io/com.oath.cyclops/ cyclops / 10.0.0-FINAL / cyclops / control / Either.html (3认同)

Vla*_*eev 5

不,没有。

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

  • 您有资料来源吗? (8认同)
  • 我也反对Either 自然出现,对我来说,在一个流中,一个map 操作可能会抛出一个异常,所以我映射到一个Either&lt;Exception, Result&gt; 流 (4认同)
  • @akroy,这似乎是正确的,Brian Goetz在此答案中写了很多:[link](http://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555# 26328555)。 (2认同)
  • 对我来说,“Either”确实是自然产生的。也许我做错了。当一个方法可以返回两个不同的东西时,你会怎么做?比如,`Either&lt;List&lt;String&gt;, SomeOtherClass&gt;`? (2认同)

Dom*_*Fox 5

Either在小型库“ ambivalence”中有一个独立的实现:http : //github.com/poetix/ambivalence

您可以从Maven Central获得它:

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>ambivalence</artifactId>
    <version>0.2</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)


whi*_*win 5

lambda-companion有一个Either类型(以及一些其他功能类型,例如Try)

<dependency>
    <groupId>no.finn.lambda</groupId>
    <artifactId>lambda-companion</artifactId>
    <version>0.25</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

使用它很容易:

final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())
Run Code Online (Sandbox Code Playgroud)

  • 项目不再维护。 (3认同)