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]?
Antworten:
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.
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.
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.
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).
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...
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.
Siehe Atlassische Fuge . Es gibt dort eine gute Umsetzung Either.
In der Java-Standardbibliothek gibt es keine. Es gibt jedoch eine Implementierung von Entweder in FunctionalJava , zusammen mit vielen anderen netten Klassen.
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.
Xorwurde umbenannt zu Either: in Zyklopen X static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/...
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.
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>?
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>
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())