Все еще нет стандартной реализации за все это время :(
Cherry
Всегда бывает java.lang.Object ... грустно.
Alex R
Ответы:
68
Нет Eitherтипа Java 8, поэтому вам нужно создать его самостоятельно или использовать какую-то стороннюю библиотеку.
Вы можете создать такую функцию, используя новый Optionalтип (но прочитайте этот ответ до конца):
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);
}
}
Пример использования:
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:
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; };
}
}
Это легко изменить на строгий отказ от него null, просто вставив Objects.requireNonNull(value)в начале обоих фабричных методов. Точно так же можно было бы вообразить добавление поддержки для пустого.
Имейте в виду , что в то время как это ведет себя как Eitherтип в каком - то смысле «слишком большой», так как ваши leftи rightполя в принципе может и быть пустым или оба определены. Вы скрыли конструкторы, которые сделали бы это возможным, но такой подход по-прежнему оставляет возможность ошибок в вашей реализации. Говоря простым языком арифметики, вы пытаетесь выйти a + bиз (1 + a) * (1 + b). Конечно, a + bвозникает в результате этого выражения, но то же самое 1и a * b.
Таинственный Дэн
6
@ Таинственный Дэн: запретить определенные состояния во время создания объекта - предпочтительный способ в Java. В противном случае вам пришлось бы изобретать новый тип «допустимого диапазона» почти для каждого варианта использования, intпоскольку использование всего диапазона значений intпри использовании intпеременной является исключением, как пример. В конце концов, он Optionalделает то же самое, обеспечивая соблюдение инвариантов во время создания объекта.
Хольгер
1
@Holger: 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).
Чарли
2
@charlie: это решение не учитывает, что Optional.mapпозволяет функции возвращаться null, превращая ее в пустую Optional. Однако, кроме возможности обнаружить это и сразу бросить, я не вижу альтернативного решения, которое было бы «более правильным». Afaik, здесь нет эталонного поведения, так как в Scala вы не можете сопоставить с null…
Хольгер
1
@Holger: Я согласен, что нет "более правильного" способа, мне просто не понравилось, что Rightпуть кода вообще выполняется для Leftэкземпляра, даже если ошибка та же. И да, я бы предпочел потерпеть неудачу сразу, а не получить <empty, empty>и потерпеть неудачу позже. Но опять же, все дело только в вкусе / стиле.
Чарли
26
На момент написания vavr (ранее javaslang), вероятно, была самой популярной функциональной библиотекой Java 8. Это очень похоже на lambda-companion Either в моем другом ответе.
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
Разработчики Java язык явно утверждают , что типы , такие как Option<T>предназначены для использования только в качестве временных значений (например , в потоке операций результатов), так что пока они являются то же самое , как и в других языках, они не должны быть использованы , поскольку они используются в других языков. Поэтому неудивительно, что нет такой вещи, Eitherпотому что она не возникает естественным образом (например, из потоковых операций), как это Optionalпроисходит.
@akroy, похоже, это правильно, Брайан Гетц написал об этом в своем ответе: ссылка .
RonyHe 01
2
Для меня Eitherвозникает естественно. Может я не так делаю. Что делать, если метод может возвращать две разные вещи? Типа Either<List<String>, SomeOtherClass>?
tamas.kenez
3
Я бы также возразил, что Either возникает естественным образом для меня в потоке, где операция карты потенциально может вызвать исключение, поэтому я сопоставляю поток Either <Exception, Result>
java.lang.Object
... грустно.Ответы:
Нет
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>
и потерпеть неудачу позже. Но опять же, все дело только в вкусе / стиле.На момент написания vavr (ранее javaslang), вероятно, была самой популярной функциональной библиотекой Java 8. Это очень похоже на lambda-companion Either в моем другом ответе.
Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();
источник
См. Атласскую фугу . Есть хорошая реализация
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())
источник