Так само, як java.util.Optional<T>у Java 8 (дещо) еквівалент Option[T]типу Scala , чи існує еквівалент Scala Either[L, R]?
Відповіді:
Немає Either типу Java 8, тому вам потрібно створити його самостійно або скористатися якоюсь сторонньою бібліотекою.
Ви можете створити таку функцію, використовуючи новий Optionalтип (але прочитайте до кінця цієї відповіді):
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);
}
}
Приклад використання:
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));}
));
Ретроспективно, Optionalзасноване рішення більше нагадує академічний приклад, але не є рекомендованим підходом. Однією з проблем є лікуванняnull „порожнього”, що суперечить значенню „будь-якого”.
Наступний код показує значення, Eitherяке розглядає nullможливе значення, тому воно суворо "або", ліворуч або праворуч, навіть якщо значення 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; };
}
}
Це легко змінити на суворе відхилення null, просто вставивши Objects.requireNonNull(value)на початку обох заводських методів знак . Так само можна було б уявити додавання підтримки для порожнього.
Either, тип в якомусь сенсі є "занадто великим", оскільки ваші leftі rightполя в принципі можуть бути порожніми або обидва бути визначеними. Ви приховали конструктори, які б це зробили можливим, але підхід все ще залишає потенційні помилки у вашій реалізації. У простих типів арифметичних термінів, ви намагаєтеся отримати a + bз (1 + a) * (1 + b). Звичайно, це a + bвідбувається в результаті цього виразу, але так само є 1і a * b.
intоскільки використання всього діапазону значень intпри використанні intзмінної є винятком, як приклад. Зрештою, Optionalробить те саме, застосовуючи інваріанти під час будівництва об'єкта.
Either.left(42).map(left -> null, right -> right)кидає NoSuchElementException(правильно) на this.right.get()(неправильно). Крім того , можна обійти виконання інваріанти і продукції Either<empty, empty>по Either.left(42).mapLeft(left -> null). Або, зібравши, знову не вдасться Either.left(42).mapLeft(left -> null).map(left -> left, right -> right).
Optional.mapможливості повернення функції null, перетворюючи її на порожню Optional. Однак, крім можливості виявити це і кинути негайно, я не бачу жодного альтернативного рішення, яке було б "більш правильним". Afaik, немає посилальної поведінки, як у Scala, ти не можеш зіставити null...
Rightшлях коду виконується взагалі для Leftекземпляра, хоча помилка однакова. І так, я віддав би перевагу невдачі одразу, а не отриманню <empty, empty>та невдачі згодом. Але знову ж таки, це все лише питання смаку / стилю.
Див. Атласова фуга . Там є хороша реалізація Either.
У стандартній бібліотеці Java немає жодного. Однак є реалізація Either у FunctionalJava , поряд з багатьма іншими приємними класами.
cyclops- response має «правильну» упереджену реалізацію під назвою 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
Існує також пов’язаний тип Ior, який може виступати як один, так і кортеж2.
Xorбуло перейменовано на EitherCyclops X: static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/…
Ні, немає.
Розробники мови Java прямо заявляють, що типи типу Option<T>призначені для використання лише як тимчасові значення (наприклад, у результатах потокових операцій), тому, хоча вони такі самі, як і в інших мовах, вони не повинні використовуватися, як вони використовуються в інших мови. Тож не дивно, що такого поняття не існує, Eitherоскільки воно не виникає природним шляхом (наприклад, з потокових операцій), як Optionalце відбувається.
Eitherсправді виникає природно. Можливо, я роблю це неправильно. Що ви робите, коли метод може повернути дві різні речі? Подобається Either<List<String>, SomeOtherClass>,?
Існує окрема реалізація Eitherв невеликій бібліотеці "ambivalence": http://github.com/poetix/ambivalence
Ви можете отримати його у Maven central:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
лямбда-супутник має Eitherтип (і кілька інших функціональних типів, наприклад Try)
<dependency>
<groupId>no.finn.lambda</groupId>
<artifactId>lambda-companion</artifactId>
<version>0.25</version>
</dependency>
Користуватися ним просто:
final String myValue = Either.right("example").fold(failure -> handleFailure(failure), Function.identity())