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 classEither<L,R>
{
publicstatic <L,R> Either<L,R> left(L value) {
returnnew Either<>(Optional.of(value), Optional.empty());
}
publicstatic <L,R> Either<L,R> right(R value) {
returnnew Either<>(Optional.empty(), Optional.of(value));
}
private final Optional<L> left;
private final Optional<R> right;
privateEither(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)
{
returnnew Either<>(left.map(lFunc),right);
}
public <T> Either<L,T> mapRight(Function<? super R, ? extends T> rFunc)
{
returnnew Either<>(left, right.map(rFunc));
}
publicvoidapply(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:
abstractclassEither<L,R>
{
publicstatic <L,R> Either<L,R> left(L value) {
returnnew Either<L,R>() {
@Overridepublic <T> T map(Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc) {
return lFunc.apply(value);
}
};
}
publicstatic <L,R> Either<L,R> right(R value) {
returnnew Either<L,R>() {
@Overridepublic <T> T map(Function<? super L, ? extends T> lFunc,
Function<? super R, ? extends T> rFunc) {
return rFunc.apply(value);
}
};
}
privateEither() {}
publicabstract <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) {
returnthis.<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) {
returnthis.<Either<L,T>>map(t -> (Either<L,T>)this, t -> right(lFunc.apply(t)));
}
publicvoidapply(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); returnnull; };
}
}
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.
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();
In der Java-Standardbibliothek gibt es keine. Es gibt jedoch eine Implementierung von Entweder in FunctionalJava , zusammen mit vielen anderen netten Klassen.
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.
@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>
java.lang.Object
... traurig.Antworten:
Es gibt keinen
Either
Java 8-Typ, daher müssen Sie selbst einen erstellen oder eine Bibliothek eines Drittanbieters verwenden.Sie können eine solche Funktion mit dem neuen
Optional
Typ 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 Behandlungnull
als "leer", was der Bedeutung von "entweder" widerspricht.Der folgende Code zeigt einen
Either
, dernull
einen möglichen Wert berücksichtigt , also ist er streng "entweder", links oder rechts, selbst wenn der Wert istnull
: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,
null
indem einfachObjects.requireNonNull(value)
am Anfang beider Factory-Methoden ein eingefügt wird. Ebenso wäre es denkbar, Unterstützung für ein leeres hinzuzufügen.quelle
Either
der Typ zwar so verhält , aber in gewissem Sinne "zu groß" ist, da Ihreleft
und Ihreright
Felder 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 + b
herauszukommen(1 + a) * (1 + b)
. Sicher,a + b
tritt im Ergebnis dieses Ausdrucks auf, aber so ist1
unda * b
.int
da die Verwendung des gesamten Wertebereichsint
bei Verwendung einerint
Variablen nur als Beispiel die Ausnahme darstellt. Dies giltOptional
auch für das Erzwingen der Invarianten während der Objektkonstruktion.Either.left(42).map(left -> null, right -> right)
wirftNoSuchElementException
(richtig) aufthis.right.get()
(falsch). Auch kann man die Invarianten Durchsetzung und Produkten umgehenEither<empty, empty>
durchEither.left(42).mapLeft(left -> null)
. Oder wenn zusammengesetzt, scheitern Sie erneutEither.left(42).mapLeft(left -> null).map(left -> left, right -> right)
.Optional.map
dass die Funktion zurückkehrennull
und leer werden kannOptional
. 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 zuordnennull
...Right
für eineLeft
Instanz ü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.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();
quelle
Siehe Atlassische Fuge . Es gibt dort eine gute Umsetzung
Either
.quelle
In der Java-Standardbibliothek gibt es keine. Es gibt jedoch eine Implementierung von Entweder in FunctionalJava , zusammen mit vielen anderen netten Klassen.
quelle
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.
quelle
Xor
wurde umbenannt zuEither
: 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,Either
weil es nicht auf natürliche Weise (z. B. durch Stream-Operationen)Optional
entsteht.quelle
Either
entsteht natürlich. Vielleicht mache ich es falsch. Was tun Sie, wenn eine Methode zwei verschiedene Dinge zurückgeben kann? WieEither<List<String>, SomeOtherClass>
?Either
In einer kleinen Bibliothek gibt es eine eigenständige Implementierung von "Ambivalenz": http://github.com/poetix/ambivalenceSie können es von Maven Central bekommen:
<dependency> <groupId>com.codepoetics</groupId> <artifactId>ambivalence</artifactId> <version>0.2</version> </dependency>
quelle
Lambda-Begleiter hat einen
Either
Typen (und ein paar anderen Funktionstypen zBTry
)<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())
quelle