Gibt es ein Äquivalent zu Scalas Entweder in Java 8?

80

Genau wie java.util.Optional<T>in Java 8 (etwas) dem Option[T]Typ von Scala entspricht , gibt es ein Äquivalent zu dem von Scala Either[L, R]?

HRJ
quelle
Nach all dieser Zeit immer noch keine Standardimplementierung :(
Cherry
Es ist immer java.lang.Object ... traurig.
Alex R

Antworten:

68

Es gibt keinen EitherJava 8-Typ, daher müssen Sie selbst einen erstellen oder eine Bibliothek eines Drittanbieters verwenden.

Sie können eine solche Funktion mit dem neuen OptionalTyp erstellen (aber bis zum Ende dieser Antwort lesen):

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

Beispiel für einen Anwendungsfall:

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

Rückblickend Optionalähnelt die basierte Lösung eher einem akademischen Beispiel, ist jedoch kein empfohlener Ansatz. Ein Problem ist die Behandlung nullals "leer", was der Bedeutung von "entweder" widerspricht.

Der folgende Code zeigt einen Either, der nulleinen möglichen Wert berücksichtigt , also ist er streng "entweder", links oder rechts, selbst wenn der Wert ist 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; };
    }
}

Es ist einfach, dies in eine strikte Ablehnung zu ändern, nullindem einfach Objects.requireNonNull(value)am Anfang beider Factory-Methoden ein eingefügt wird. Ebenso wäre es denkbar, Unterstützung für ein leeres hinzuzufügen.

Holger
quelle
11
Denken Sie daran, dass Eitherder Typ zwar so verhält , aber in gewissem Sinne "zu groß" ist, da Ihre leftund Ihre rightFelder im Prinzip beide leer oder beide definiert sein können. Sie haben die Konstruktoren versteckt, die dies ermöglichen würden, aber der Ansatz lässt immer noch das Potenzial für Fehler in Ihrer Implementierung. In einfachen arithmetischen Begriffen versuchen Sie, a + bherauszukommen (1 + a) * (1 + b). Sicher, a + btritt im Ergebnis dieses Ausdrucks auf, aber so ist 1und a * b.
Geheimnisvoller Dan
6
@Mysterious Dan: Das Verbieten bestimmter Zustände während der Objektkonstruktion ist in Java der bevorzugte Weg. Andernfalls müssten Sie für fast jeden Anwendungsfall einen neuen Typ "gültiger Bereich" erfinden, intda die Verwendung des gesamten Wertebereichs intbei Verwendung einer intVariablen nur als Beispiel die Ausnahme darstellt. Dies gilt Optionalauch für das Erzwingen der Invarianten während der Objektkonstruktion.
Holger
1
@Holger: Either.left(42).map(left -> null, right -> right)wirft NoSuchElementException(richtig) auf this.right.get()(falsch). Auch kann man die Invarianten Durchsetzung und Produkten umgehen Either<empty, empty>durch Either.left(42).mapLeft(left -> null). Oder wenn zusammengesetzt, scheitern Sie erneut Either.left(42).mapLeft(left -> null).map(left -> left, right -> right).
Charlie
2
@charlie: Diese Lösung berücksichtigt nicht, Optional.mapdass die Funktion zurückkehren nullund leer werden kann Optional. Abgesehen von der Möglichkeit, dies zu erkennen und sofort zu werfen, sehe ich keine alternative Lösung, die „korrekter“ ist. Afaik, es gibt kein Referenzverhalten, wie in Scala, kann man nicht zuordnen null...
Holger
1
@Holger: Ich stimme zu, dass es keinen "korrekteren" Weg gibt. Mir hat einfach nicht gefallen, dass der Codepfad Rightfür eine LeftInstanz überhaupt ausgeführt wird, obwohl der Fehler der gleiche ist. Und ja, ich würde es vorziehen, sofort zu scheitern, anstatt eine zu erhalten <empty, empty>und später zu scheitern. Aber auch hier ist alles nur eine Frage des Geschmacks / Stils.
Charlie
26

Zum Zeitpunkt des Schreibens ist vavr (ehemals javaslang) wahrscheinlich die beliebteste funktionale Java 8-Bibliothek. Es ist ziemlich ähnlich zu Lambda-Begleiter entweder in meiner anderen Antwort.

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

In der Java-Standardbibliothek gibt es keine. Es gibt jedoch eine Implementierung von Entweder in FunctionalJava , zusammen mit vielen anderen netten Klassen.

Ravi Kiran
quelle
Der Link zu Entweder scheint gelöscht worden zu sein. Sind Sie sicher, dass das Projekt noch unterstützt wird?
Flame_Phoenix
Ich habe den Link aktualisiert. Das Projekt wird AFAIK weiterhin aktiv gepflegt.
Ravi Kiran
Es ist, aber die Dokumentation ist miserabel. :(
Mischa Tavkhelidze
10

Cyclops- React hat eine 'richtige' voreingenommene Implementierung namens 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

Es gibt auch einen verwandten Typ Ior, der entweder oder als Tupel2 fungieren kann.

  • Offenlegung Ich bin der Autor von Cyclops-React.
John McClean
quelle
6

Nein, es gibt keine.

Java - Entwickler explizit fest , dass mögen Typen Option<T>sollen nur als temporärer Wert verwendet werden (zB in Stream - Operationen Ergebnisse), also , während sie sind das gleiche wie in anderen Sprachen, werden sie angeblich nicht verwendet werden , da sie in andere verwendet werden , Sprachen. Es ist daher nicht verwunderlich, dass es so etwas nicht gibt, Eitherweil es nicht auf natürliche Weise (z. B. durch Stream-Operationen) Optionalentsteht.

Vladimir Matveev
quelle
8
Hast du eine Quelle dazu?
Akroy
3
@akroy, das scheint richtig zu sein, Brian Goetz schrieb so viel in dieser Antwort: Link .
RonyHe
2
Für mich Eitherentsteht natürlich. Vielleicht mache ich es falsch. Was tun Sie, wenn eine Methode zwei verschiedene Dinge zurückgeben kann? Wie Either<List<String>, SomeOtherClass>?
Tamas.kenez
3
Ich würde auch einwenden, dass entweder natürlich für mich in einem Stream auftritt, in dem eine Kartenoperation möglicherweise eine Ausnahme auslösen könnte, also ordne ich einem Stream von entweder <Ausnahme, Ergebnis>
Olivier Gérardin
6

EitherIn einer kleinen Bibliothek gibt es eine eigenständige Implementierung von "Ambivalenz": http://github.com/poetix/ambivalence

Sie können es von Maven Central bekommen:

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>ambivalence</artifactId>
    <version>0.2</version>
</dependency>
Dominic Fox
quelle
5

Lambda-Begleiter hat einen EitherTypen (und ein paar anderen Funktionstypen zB Try)

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

Die Verwendung ist einfach:

final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())
Wirbel
quelle
1
Das Projekt wird nicht mehr gewartet.
Flame_Phoenix