Так же, как 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. Однако есть реализация Either в FunctionalJava , наряду со многими другими классными классами.
cyclops-react имеет «правильную» смещенную реализацию под названием 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был переименован в Eitherв Циклопа X: static.javadoc.io/com.oath.cyclops/cyclops/10.0.0-FINAL/cyclops/...
Нет, нет.
Разработчики Java язык явно утверждают , что типы , такие как Option<T>предназначены для использования только в качестве временных значений (например , в потоке операций результатов), так что пока они являются то же самое , как и в других языках, они не должны быть использованы , поскольку они используются в других языков. Поэтому неудивительно, что нет такой вещи, Eitherпотому что она не возникает естественным образом (например, из потоковых операций), как это Optionalпроисходит.
Eitherвозникает естественно. Может я не так делаю. Что делать, если метод может возвращать две разные вещи? Типа Either<List<String>, SomeOtherClass>?
Существует автономная реализация Eitherв небольшой библиотеке «амбивалентности»: 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())