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 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 Behandlung null
als "leer", was der Bedeutung von "entweder" widerspricht.
Der folgende Code zeigt einen Either
, der null
einen 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, null
indem 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.
Either
der Typ zwar so verhält , aber in gewissem Sinne "zu groß" ist, da Ihre left
und Ihre right
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 ist 1
und a * b
.
int
da die Verwendung des gesamten Wertebereichs int
bei Verwendung einer int
Variablen nur als Beispiel die Ausnahme darstellt. Dies gilt Optional
auch 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.map
dass die Funktion zurückkehren null
und 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
...
Right
für eine Left
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.
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.
Xor
wurde 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, Either
weil es nicht auf natürliche Weise (z. B. durch Stream-Operationen) Optional
entsteht.
Either
entsteht natürlich. Vielleicht mache ich es falsch. Was tun Sie, wenn eine Methode zwei verschiedene Dinge zurückgeben kann? Wie Either<List<String>, SomeOtherClass>
?
Either
In 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 Either
Typen (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())