在 Java8中是否有类似于 Scala 的任意一种的东西?

就像 Java8中的 java.util.Optional<T>(在某种程度上)等价于 Scala 的 Option[T]类型一样,有没有等价于 Scala 的 Either[L, R]

64256 次浏览

No, there is none.

Java language developers explicitly state that types like Option<T> are intended to be used only as temporary values (e.g. in stream operations results), so while they are the same thing as in other languages, they are not supposed to be used as they are used in other languages. So it is not surprising that there is no such thing as Either because it does not arise naturally (e.g. from stream operations) like Optional does.

There is no Either in the Java Standard Library. However there is an implementation of Either in FunctionalJava, along with many other nice classes.

There is no Either type is Java 8, so you need to create one yourself or use some third-party library.

You may build such a feature using the new Optional type (but read to the end of this answer):

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);
}
}

Example use case:

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));}
));

In retrospective, the Optional based solution is more like an academic example, but not a recommended approach. One problem is the treatment of null as “empty” which contradicts the meaning of “either”.

The following code shows an Either that considers null a possible value, so it’s strictly “either”, left or right, even if the value is 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; };
}
}

It’s easy to change that to a strict rejection of null by simply inserting an Objects.requireNonNull(value) at the beginning of both factory methods. Likewise, adding support for an empty either would be imaginable.

There is a stand-alone implementation of Either in a small library, "ambivalence": http://github.com/poetix/ambivalence

You can get it from Maven central:

<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>

lambda-companion has an Either type (and a few other functional types e.g. Try)

<dependency>
<groupId>no.finn.lambda</groupId>
<artifactId>lambda-companion</artifactId>
<version>0.25</version>
</dependency>

Using it is easy:

final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())

At the time of writing, vavr (formerly javaslang) is probably the most popular functional Java 8 library. It is pretty similar to lambda-companion's Either in my other answer.

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

cyclops-react has a 'right' biased either implementation called 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

There is also a related type Ior which can act as an either or a tuple2.

  • disclosure I am the author of cyclops-react.