Необов’язково абоElse Необов’язково на Java


137

Я працював з новим додатковим типом в Java 8 , і я натрапив на те, що схоже на звичайну операцію, яка не підтримується функціонально: "orElseOptions"

Розглянемо таку схему:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

Існує багато форм цього шаблону, але він зводиться до того, щоб бажати "orElse" на необов'язковій формі, яка приймає функцію, що створює новий необов'язковий, іменується лише якщо поточного не існує.

Її реалізація виглядатиме так:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

Мені цікаво, чи є причина, що такий метод не існує, якщо я просто недбало використовую необов’язковий і які інші способи люди придумали для вирішення цієї справи.

Слід сказати, що я думаю, що рішення, що включають власні класи / методи корисних програм, не є витонченими, оскільки люди, які працюють з моїм кодом, не обов'язково знають, що вони існують.

Крім того, якщо хтось знає, чи буде такий метод включений до JDK 9, і де я можу запропонувати такий метод? Це здається мені досить кричущим упущенням API.


13
Дивіться це питання . Для уточнення: це вже буде в Java 9 - якщо не в майбутньому оновлення Java 8.
Obicere

Це є! Дякую, не знайшли цього в пошуку.
Yona Appletree

2
@Obicere Це питання не стосується тут, оскільки йдеться про поведінку на порожньому Необов’язково, а не про альтернативний результат . Необов’язково вже є orElseGet()те, для чого потрібна ОП, тільки вона не створює приємного каскадного синтаксису.
Марко Топольник


1
Чудові підручники на Java Необов’язково: codeflex.co/java-optional-no-more-nullpointerexception
Джон Детройт

Відповіді:


86

Це частина JDK 9 у формі or, яка приймає a Supplier<Optional<T>>. Тоді ваш приклад:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

Детальніше дивіться Javadoc або цю публікацію, яку я написав.


Приємно. Це доповнення повинно бути рік, і я не помітив. Що стосується питання у вашому блозі, зміна типу повернення призведе до порушення бінарної сумісності, оскільки інструкції виклику байт-коду стосуються повної підпису, включаючи тип повернення, тому немає шансів змінити тип повернення ifPresent. Але так чи інакше, я думаю, ім’я ifPresentвсе одно не добре. Для всіх інших методів , не маючи «Else» в назві (як map, filter, flatMap), мається на увазі , що вони не роблять нічого , якщо значення не присутній, так навіщо ifPresent...
Хольгер

Таким чином, додавання Optional<T> perform(Consumer<T> c)методу, щоб дозволити ланцюжок perform(x).orElseDo(y)( orElseDoяк альтернатива запропонованому вами запропонованому ifEmpty, бути узгодженим із наявністю elseв імені всіх методів, які можуть зробити щось для відсутніх значень). Ви можете імітувати це performв Java 9 через, stream().peek(x).findFirst()хоча це зловживання API, і досі немає способу його виконати, Runnableне вказуючи Consumerодночасно…
Holger

65

Найбільш чистим підходом "спробувати послуги" з урахуванням поточного API був би:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

Важливим аспектом є не (постійний) ланцюг операцій, про який потрібно писати один раз, а те, як легко додати іншу послугу (або змінити список послуг, загальний). Тут ()->serviceX(args)достатньо додати або видалити сингл .

Через ледачу оцінку потоків жодна послуга не буде викликана, якщо попередня служба повертається не порожньою Optional.


13
щойно використав це у проекті, слава богу, ми не робимо огляди коду.
Ілля Смагін

3
Це набагато чистіше, ніж ланцюжок "orElseGet", але читати його також набагато складніше.
slartidan

4
Це, звичайно, правильно ... але, чесно кажучи, мені потрібна секунда, щоб проаналізувати це, і я не відвертаюсь із впевненістю, що це правильно. Зауважте, я розумію, що цей приклад є, але я міг уявити собі невелику зміну, яка б змусила його не ліниво оцінюватись або мати якусь іншу помилку, яку не можна було б розрізнити з першого погляду. По-моєму, це відноситься до категорії «пристойні» як «корисні» функції.
Йона Апплетре

3
Цікаво, якщо комутація .map(Optional::get)з .findFirst()полегшить «читання», наприклад, .filter(Optional::isPresent).findFirst().map(Optional::get)чи можна «прочитати» як «знайти перший елемент у потоці, для якого необов’язковий :: isPresent є істинним, а потім вирівняти його, застосувавши необов’язково :: get»?
schatten

3
Смішно, я розмістив дуже подібне рішення для подібного питання кілька місяців тому. Це вперше я стикаюся з цим.
шмосель

34

Це не дуже, але це спрацює:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup)є досить зручним малюнком для використання з Optional. Це означає "Якщо це Optionalмістить значення v, дайте мені func(v), інакше дайте меніsup.get() ".

У цьому випадку ми телефонуємо serviceA(args)та отримуємо Optional<Result>. Якщо це Optionalмістить значення v, ми хочемо отримати Optional.of(v), але якщо воно порожнє, ми хочемо отриматиserviceB(args) . Промийте-повторіть з більшою кількістю альтернатив.

Інші способи використання цього шаблону є

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)

1
так, коли я використовую цю стратегію, eclipse говорить: "Метод orElseGet (постачальник <? розширює рядок>) у типі Необов'язковий <String> не застосовується для аргументів (() -> {})" Це не здається вважати повернення Факультативної дійсною стратегією на String ??
chrismarx

@chrismarx () -> {}не повертає Optional. Що ви намагаєтеся досягти?
Міша

1
Я просто намагаюся наслідувати приклад. Зв'язування дзвінків на карті не працює. Мої служби повертають рядки, і, звичайно, немає доступних варіантів
.map

1
Прекрасна читаемая альтернатива майбутньому or(Supplier<Optional<T>>)Java 9
David M.

1
@Seepy ви помиляєтесь. .map()на порожній Optionalбуде виробляти порожнє Optional.
Міша

27

Можливо, це те, що ви прагнете: Отримайте цінність від одного необов’язкового чи іншого

В іншому випадку ви можете поглянути Optional.orElseGet. Ось приклад того, що я думаю, що ви хочете:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));

2
Це те, що зазвичай роблять генії. Оцінювати необов’язковість як нульову, і загортання її ofNullable- найкрутіша річ, яку я коли-небудь бачив.
Джин Квон

5

Якщо припустити, що ви все ще на JDK8, є кілька варіантів.

Варіант №1: складіть власний помічник

Наприклад:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

Щоб ви могли:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

Варіант №2: використовувати бібліотеку

Наприклад, or()опція google guava підтримує належну операцію (як і JDK9), наприклад:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(Де кожна служба повертається com.google.common.base.Optional, а не java.util.Optional).


У Optional<T>.or(Supplier<Optional<T>>)документах Guava я не знайшов . У вас є посилання на це?
Тамас Гегед

.orElseGet повертає T, але необов’язково: порожній повертається Необов’язково <T>. Необов'язково.ofNullable (........ orElse (null)) надає бажаний ефект, як описано @aioobe.
Мігель Перейра

@TamasHegedus Ви маєте на увазі у варіанті №1? Це спеціальна реалізація, яка знаходиться на фрагменті над ним. ps: вибачте за пізню відповідь
Sheepy

@MiguelPereira .orElseGet у цьому випадку повертає необов’язково <T>, оскільки він працює на Факультативний <Факультативний <T>>
Sheepy

2

Це виглядає як добре підходить для узгодження шаблонів і більш традиційний інтерфейс Option з деякими та жодними реалізаціями (такими, як у Javaslang , FunctionalJava ), або ледачий, можливо, реалізація в циклоп-реагуванні. Я автор цієї бібліотеки.

З циклоп-реагуванням ви також можете використовувати структурну схему відповідності для типів JDK. За бажанням ви можете порівнювати справжній та відсутній справи за схемою відвідувачів . це виглядатиме приблизно так -

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.