Wciąż brak standardowej implementacji po tak długim czasie :(
Cherry,
Zawsze jest java.lang.Object ... smutno.
Alex R
Odpowiedzi:
68
Nie ma Eithertypu Java 8, więc musisz utworzyć ją samodzielnie lub skorzystać z biblioteki innej firmy.
Możesz zbudować taką funkcję za pomocą nowego Optionaltypu (ale przeczytaj do końca tej odpowiedzi):
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);
}
}
Przykład użycia:
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));}
));
Z perspektywy czasu, Optionalrozwiązanie oparte bardziej przypomina akademicki przykład, ale nie jest zalecane. Jednym z problemów jest traktowanie nulljako „pustego”, co jest sprzeczne ze znaczeniem „albo”.
Poniższy kod przedstawia kod Eitheruwzględniający nullmożliwą wartość, więc jest to ściśle „albo”, lewy lub prawy, nawet jeśli wartość to 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; };
}
}
Łatwo to zmienić na ścisłe odrzucenie null, po prostu wstawiając znak Objects.requireNonNull(value)na początku obu metod fabrycznych. Podobnie, można sobie wyobrazić dodanie obsługi pustego obu.
Pamiętaj, że chociaż zachowuje się tak Either, typ jest w pewnym sensie „za duży”, ponieważ pola lefti rightpola mogą w zasadzie być puste lub oba mogą być zdefiniowane. Ukryłeś konstruktory, które by to umożliwiały, ale to podejście nadal pozostawia potencjalne błędy w twojej implementacji. W typie prostych arytmetycznych warunkach, starasz się dostać a + bz (1 + a) * (1 + b). Jasne, a + bpojawia się w wyniku tego wyrażenia, ale tak jest 1i a * b.
Tajemniczy Dan
6
@Tajemniczy Dan: zakazanie określonego stanu podczas budowy obiektu jest preferowanym sposobem w Javie. W przeciwnym razie musiałbyś wymyślić nowy „prawidłowy zakres” dla prawie każdego przypadku użycia, intponieważ używanie całego zakresu wartości, intgdy użycie intzmiennej jest wyjątkiem, tak jak na przykładzie. W końcu Optionalrobi to samo, wymuszając niezmienniki podczas budowy obiektu.
Holger
1
@Holger: Either.left(42).map(left -> null, right -> right)rzuca NoSuchElementException(poprawne) na this.right.get()(niepoprawne). Można również ominąć wymuszanie i wytwarzanie niezmienników Either<empty, empty>do Either.left(42).mapLeft(left -> null). Lub po złożeniu, znowu nie Either.left(42).mapLeft(left -> null).map(left -> left, right -> right).
charlie
2
@charlie: to rozwiązanie nie uwzględnia Optional.mapmożliwości powrotu funkcji null, zmieniając ją w pustą Optional. Jednak poza możliwością wykrycia tego i natychmiastowego wyrzucenia nie widzę żadnego alternatywnego rozwiązania, które byłoby „bardziej poprawne”. Ahaik, nie ma zachowania referencyjnego, tak jak w Scali, nie możesz zmapować do null…
Holger
1
@Holger: Zgadzam się, że nie ma bardziej poprawnego sposobu, po prostu nie podobał mi się fakt, że Rightścieżka kodu jest w ogóle wykonywana na Leftprzykład, mimo że błąd jest ten sam. I tak, wolałbym ponieść porażkę od razu, zamiast otrzymać <empty, empty>i oblać później. Ale znowu wszystko jest kwestią gustu / stylu.
charlie
26
W chwili pisania tego tekstu vavr (dawniej javaslang) jest prawdopodobnie najpopularniejszą funkcjonalną biblioteką Java 8. Jest całkiem podobny do lambda-companion albo w mojej drugiej odpowiedzi.
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
W bibliotece Java Standard Library nie ma żadnego żadnego. Jednak istnieje implementacja Either w FunctionalJava , wraz z wieloma innymi ładnymi klasami.
Programiści języka Java wyraźnie stwierdzają, że typy takie jak Option<T>mają być używane tylko jako wartości tymczasowe (np. W wynikach operacji strumieniowych), więc chociaż są tym samym, co w innych językach, nie powinny być używane tak, jak są używane w innych Języki. Nic więc dziwnego, że nie ma czegoś takiego jakEither to, że nie powstaje naturalnie (np. Z operacji strumieniowych) tak jak Optionalma.
@akroy, to wydaje się być poprawne, Brian Goetz napisał to samo w tej odpowiedzi: link .
RonyHe
2
U mnie Eitherpojawia się naturalnie. Może robię to źle. Co robisz, gdy metoda może zwrócić dwie różne rzeczy? Podobnie jak Either<List<String>, SomeOtherClass>?
tamas.kenez
3
Sprzeciwiłbym się również temu, że Albo pojawia się naturalnie, dla mnie w strumieniu, w którym operacja mapy mogłaby potencjalnie zgłosić wyjątek, więc mapuję na strumień albo <Wyjątek, Wynik>
java.lang.Object
... smutno.Odpowiedzi:
Nie ma
Either
typu Java 8, więc musisz utworzyć ją samodzielnie lub skorzystać z biblioteki innej firmy.Możesz zbudować taką funkcję za pomocą nowego
Optional
typu (ale przeczytaj do końca tej odpowiedzi):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); } }
Przykład użycia:
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));} ));
Z perspektywy czasu,
Optional
rozwiązanie oparte bardziej przypomina akademicki przykład, ale nie jest zalecane. Jednym z problemów jest traktowanienull
jako „pustego”, co jest sprzeczne ze znaczeniem „albo”.Poniższy kod przedstawia kod
Either
uwzględniającynull
możliwą wartość, więc jest to ściśle „albo”, lewy lub prawy, nawet jeśli wartość tonull
: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; }; } }
Łatwo to zmienić na ścisłe odrzucenie
null
, po prostu wstawiając znakObjects.requireNonNull(value)
na początku obu metod fabrycznych. Podobnie, można sobie wyobrazić dodanie obsługi pustego obu.źródło
Either
, typ jest w pewnym sensie „za duży”, ponieważ polaleft
iright
pola mogą w zasadzie być puste lub oba mogą być zdefiniowane. Ukryłeś konstruktory, które by to umożliwiały, ale to podejście nadal pozostawia potencjalne błędy w twojej implementacji. W typie prostych arytmetycznych warunkach, starasz się dostaća + b
z(1 + a) * (1 + b)
. Jasne,a + b
pojawia się w wyniku tego wyrażenia, ale tak jest1
ia * b
.int
ponieważ używanie całego zakresu wartości,int
gdy użycieint
zmiennej jest wyjątkiem, tak jak na przykładzie. W końcuOptional
robi to samo, wymuszając niezmienniki podczas budowy obiektu.Either.left(42).map(left -> null, right -> right)
rzucaNoSuchElementException
(poprawne) nathis.right.get()
(niepoprawne). Można również ominąć wymuszanie i wytwarzanie niezmiennikówEither<empty, empty>
doEither.left(42).mapLeft(left -> null)
. Lub po złożeniu, znowu nieEither.left(42).mapLeft(left -> null).map(left -> left, right -> right)
.Optional.map
możliwości powrotu funkcjinull
, zmieniając ją w pustąOptional
. Jednak poza możliwością wykrycia tego i natychmiastowego wyrzucenia nie widzę żadnego alternatywnego rozwiązania, które byłoby „bardziej poprawne”. Ahaik, nie ma zachowania referencyjnego, tak jak w Scali, nie możesz zmapować donull
…Right
ścieżka kodu jest w ogóle wykonywana naLeft
przykład, mimo że błąd jest ten sam. I tak, wolałbym ponieść porażkę od razu, zamiast otrzymać<empty, empty>
i oblać później. Ale znowu wszystko jest kwestią gustu / stylu.W chwili pisania tego tekstu vavr (dawniej javaslang) jest prawdopodobnie najpopularniejszą funkcjonalną biblioteką Java 8. Jest całkiem podobny do lambda-companion albo w mojej drugiej odpowiedzi.
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
źródło
Zobacz Atlassian Fugue . Jest tam dobra realizacja
Either
.źródło
W bibliotece Java Standard Library nie ma żadnego żadnego. Jednak istnieje implementacja Either w FunctionalJava , wraz z wieloma innymi ładnymi klasami.
źródło
Cyklop reagują ma „prawo” tendencyjne albo realizację nazwie 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
Istnieje również pokrewny typ Ior, który może działać jako albo albo krotka2.
źródło
Xor
została zmieniona naEither
w Cyclops X: static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/ ...Nie, nie ma.
Programiści języka Java wyraźnie stwierdzają, że typy takie jak
Option<T>
mają być używane tylko jako wartości tymczasowe (np. W wynikach operacji strumieniowych), więc chociaż są tym samym, co w innych językach, nie powinny być używane tak, jak są używane w innych Języki. Nic więc dziwnego, że nie ma czegoś takiego jakEither
to, że nie powstaje naturalnie (np. Z operacji strumieniowych) tak jakOptional
ma.źródło
Either
pojawia się naturalnie. Może robię to źle. Co robisz, gdy metoda może zwrócić dwie różne rzeczy? Podobnie jakEither<List<String>, SomeOtherClass>
?W
Either
małej bibliotece istnieje samodzielna implementacja „ambiwalencji”: http://github.com/poetix/ambivalenceMożesz go zdobyć z centrali Maven:
<dependency> <groupId>com.codepoetics</groupId> <artifactId>ambivalence</artifactId> <version>0.2</version> </dependency>
źródło
lambda-companion ma
Either
typ (i kilka innych funkcjonalnych typów np.Try
)<dependency> <groupId>no.finn.lambda</groupId> <artifactId>lambda-companion</artifactId> <version>0.25</version> </dependency>
Korzystanie z niego jest łatwe:
final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())
źródło