Функціональний стиль Java 8's Optional.ifPresent Java та if-not-Present?


274

У Java 8 я хочу зробити щось із Optionalоб’єктом, якщо він присутній, і зробити іншу справу, якщо його немає.

if (opt.isPresent()) {
  System.out.println("found");
} else {
  System.out.println("Not found");
}

Це, однак, не «функціональний стиль».

Optionalмає ifPresent()метод, але я не в змозі застосувати orElse()метод.

Таким чином, я не можу писати:

opt.ifPresent( x -> System.out.println("found " + x))
   .orElse( System.out.println("NOT FOUND"));

У відповідь на @assylias, я не думаю, що це Optional.map()працює для такого випадку:

opt.map( o -> {
  System.out.println("while opt is present...");
  o.setProperty(xxx);
  dao.update(o);
  return null;
}).orElseGet( () -> {
  System.out.println("create new obj");
  dao.save(new obj);
  return null;
});

У цьому випадку, коли optвін присутній, я оновлюю його властивість і зберігаю в базі даних. Коли вона недоступна, я створюю нову objі зберігаю в базі даних.

Зауважте в двох лямбдах, які я повинен повернути null.

Але коли optбуде присутній, обидва лямбда будуть виконані. objбуде оновлено, а новий об’єкт буде збережено в базі даних. Це відбувається через return nullпершу лямбду. І orElseGet()буде продовжувати страчувати.


53
Скористайтеся своїм першим зразком. Це красиво .
Сотіріос Деліманоліс

3
Я пропоную вам припинити форсувати певну поведінку під час використання API, який не призначений для такої поведінки. Ви перший приклад мені прекрасно виглядає, окрім невеликих зауважень стилю, але вони впевнені.
skiwi

4
@smallufo: замінити return null; на return o;(обидва). Однак у мене є сильне відчуття, що ти працюєш у неправильному місці. Ви повинні працювати на сайті, який це зробив Optional. На цьому місці повинен бути спосіб виконання бажаної операції без проміжного Optional.
Холгер

10
Java 9 реалізує рішення вашої проблеми: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk

2
Я думаю, що причина цього неможливо зробити легко - це цілеспрямовано. Необов’язково не слід здійснювати контроль потоку, а скоріше перетворення значення. Я знаюifPresent суперечить. Усі інші методи стосуються значення, а не дії.
AlikElzin-kilaka

Відповіді:


109

Для мене відповідь @Dane White - це добре, спершу мені не сподобалось використовувати Runnable, але я не зміг знайти альтернативи, тут інша реалізація я віддала перевагу більше

public class OptionalConsumer<T> {
    private Optional<T> optional;

    private OptionalConsumer(Optional<T> optional) {
        this.optional = optional;
    }

    public static <T> OptionalConsumer<T> of(Optional<T> optional) {
        return new OptionalConsumer<>(optional);
    }

    public OptionalConsumer<T> ifPresent(Consumer<T> c) {
        optional.ifPresent(c);
        return this;
    }

    public OptionalConsumer<T> ifNotPresent(Runnable r) {
        if (!optional.isPresent()) {
            r.run();
        }
        return this;
    }
}

Тоді :

Optional<Any> o = Optional.of(...);
OptionalConsumer.of(o).ifPresent(s ->System.out.println("isPresent "+s))
            .ifNotPresent(() -> System.out.println("! isPresent"));

Оновлення 1:

вищевикладене рішення для традиційного способу розробки, коли ви маєте значення та хочете його обробити, але що, якщо я хочу визначити функціональність та буде виконано виконання, перевірте нижче вдосконалення;

public class OptionalConsumer<T> implements Consumer<Optional<T>> {
private final Consumer<T> c;
private final Runnable r;

public OptionalConsumer(Consumer<T> c, Runnable r) {
    super();
    this.c = c;
    this.r = r;
}

public static <T> OptionalConsumer<T> of(Consumer<T> c, Runnable r) {
    return new OptionalConsumer(c, r);
}

@Override
public void accept(Optional<T> t) {
    if (t.isPresent()) {
        c.accept(t.get());
    }
    else {
        r.run();
    }
}

Потім можна використовувати як:

    Consumer<Optional<Integer>> c=OptionalConsumer.of(System.out::println, ()->{System.out.println("Not fit");});
    IntStream.range(0, 100).boxed().map(i->Optional.of(i).filter(j->j%2==0)).forEach(c);

У цьому новому коді у вас є три речі:

  1. може визначити функціональність перед наявністю об'єкта легко.
  2. не створюючи референтності об'єктів для кожного Необов’язково, лише один, у вас так менше пам'яті, ніж менше GC.
  3. він реалізує споживача для кращого використання з іншими компонентами.

до речі, тепер його назва є більш описовою, це насправді споживач>


3
Слід використовувати Optional.ofNullable (o) замість Optional.of (o)
traeper

2
Вам потрібно використовувати ofNullable, якщо ви не впевнені, що значення, яке ви збираєтеся використовувати, є нульовим чи ні, і вам не потрібно стикатися з NPE, і якщо ви впевнені, що це недійсне значення або вам не байдуже, якщо отримаєте NPE.
Бассем Реда Зохді

1
Я думаю, що клас OptionalConsumer виглядає краще, ніж якщо / else в коді. Дякую! :)
witek1902

207

Якщо ви використовуєте Java 9+, ви можете використовувати ifPresentOrElse()метод:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);

3
Приємно, бо він майже такий же чистий, як відповідність візерунків у Scala
sscarduzio

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

1
@ john16384 Добре, якщо ви вважаєте це некрасивим, тоді я видалю свою відповідь (ні).
ЖекаКозлов

Це дуже добре, але питання було розроблено спеціально для JDK8, оскільки ifPresentOrElse недоступний.
грейн

81

Представляє Java 9

ifPresentOrElse, якщо значення присутнє, виконує задану дію зі значенням, в іншому випадку виконує задану порожню дію.

Дивіться чудовий необов'язково в чіт-аркуші Java 8 .

Він пропонує всі відповіді на більшість випадків використання.

Короткий підсумок нижче

ifPresent () - зробіть щось, коли встановлено необов'язково

opt.ifPresent(x -> print(x)); 
opt.ifPresent(this::print);

filter () - відхилити (відфільтрувати) певні необов'язкові значення.

opt.filter(x -> x.contains("ab")).ifPresent(this::print);

map () - значення перетворення, якщо воно є

opt.map(String::trim).filter(t -> t.length() > 1).ifPresent(this::print);

orElse () / orElseGet () - повернення порожнього Необов’язково до T за замовчуванням

int len = opt.map(String::length).orElse(-1);
int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

orElseThrow () - ліниво викидати винятки на порожній необов’язково

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

66
Це фактично не відповідає на питання ОП. Він відповідає на багато поширених застосувань, але не на те, що запитувала ОП.
Людина капітана

1
@CaptainMan насправді це робить; вираз opt.map ("знайдено"). абоElse ("не знайдено") заповнює рахунок.
Метт

4
@ Матт ні, ОП спеціально просить дії, які він виконував, коли необов'язковий / немає, не повертати значення, коли воно є чи ні. OP навіть згадує щось подібне у питанні, використовуючи orElseGet, пояснюючи, чому це не буде працювати.
Капітан Людина

2
@CaptainMan Я бачу вашу думку. Я думаю, що він міг би змусити його працювати, якщо він не поверне нульове значення зmap , але трохи дивно просити функціональне рішення, щоб ви могли викликати DAO. Мені здається, було б більше сенсу повернути оновлений / новий об’єкт з цього map.orElseблоку, а потім зробити те, що потрібно зробити з повернутим об'єктом.
Метт

1
Я думаю, що mapфокус на самому потоці і не призначений для того, щоб "робити речі іншому об'єкту залежно від стану цього елемента в потоці". Добре знати, що ifPresentOrElseдодано в Java 9.
WesternGun

53

Альтернатива:

System.out.println(opt.map(o -> "Found")
                      .orElse("Not found"));

Я не думаю, що це покращує читабельність.

Або, як запропонував Марко, скористайтеся потрійним оператором:

System.out.println(opt.isPresent() ? "Found" : "Not found");

2
Дякую @assylias, але я не думаю, що Optional.map () працює для цього випадку (див. Моє оновлення контексту).
smallufo

2
@smallufo Вам потрібно буде повернутися new Object();в першій лямбда, але, щоб бути чесним, це стає дуже некрасивим. Я б дотримувався if / else для вашого оновленого прикладу.
Ассілія

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

40

Іншим рішенням буде використання функцій вищого порядку таким чином

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();

8
На моїх очах найкраще рішення поки що без JDK 9.
Семафор

5
Пояснення було б чудово. Я запитую себе, для чого вам потрібно використовувати карту для запуску (?) І що value -> () -> sysoозначає частина.
froehli

Дякую за це рішення! Я думаю, що причиною використання Runnable є те, що карта не повертає жодного значення, а з Runnable вона повертає лямбда, і таким чином, як результат map є лямбда, ми запускаємо її після. Тож якщо у вас є повернене значення, ви можете скористатися наступним:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik

21

Немає чудового способу зробити це поза коробкою. Якщо ви хочете регулярно використовувати ваш чистіший синтаксис, ви можете створити клас утиліти, щоб допомогти:

public class OptionalEx {
    private boolean isPresent;

    private OptionalEx(boolean isPresent) {
        this.isPresent = isPresent;
    }

    public void orElse(Runnable runner) {
        if (!isPresent) {
            runner.run();
        }
    }

    public static <T> OptionalEx ifPresent(Optional<T> opt, Consumer<? super T> consumer) {
        if (opt.isPresent()) {
            consumer.accept(opt.get());
            return new OptionalEx(true);
        }
        return new OptionalEx(false);
    }
}

Тоді ви можете використовувати статичний імпорт в інше місце, щоб отримати синтаксис, близький до того, що ви шукаєте:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));

Дякую. Це рішення прекрасне. Я знаю, що, можливо, немає вбудованого рішення (якщо JDK не включає такий метод). Ви OptionalEx дуже корисні. Все одно, дякую.
smallufo

Так, результат мені подобається та стиль, який він підтримує. Так, чому б не в стандартному API?
guthrie

Хороша відповідь. Ми робимо те саме. Я згоден, це має бути в API (або мові!), Але це було відхилено: bugs.openjdk.java.net/browse/JDK-8057557 .
Гаррет Сміт

Приємно. Це має бути частиною JDK 8.1 для розгляду.
peter_pilgrim

35
Optional.ifPresentOrElse()додано до JDK 9.
Stuart Marks

9

Якщо ви можете використовувати лише Java 8 або новішу версію:

1) якщо у вас немає spring-dataнайкращого способу:

opt.<Runnable>map(param -> () -> System.out.println(param))
      .orElse(() -> System.out.println("no-param-specified"))
      .run();

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

2) якщо вам пощастило і ви можете скористатися spring-dataнайкращим способом - необов'язково # ifPresentOrElse :

Optionals.ifPresentOrElse(opt, System.out::println,
      () -> System.out.println("no-param-specified"));

Якщо ви можете використовувати Java 9, вам обов'язково слід перейти з:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));

2

Описану поведінку можна досягти, використовуючи Vavr (раніше відомий як Javaslang), об'єктно-функціональну бібліотеку для Java 8+, яка реалізує більшість конструкцій Scala (будучи Scala - більш виразною мовою, що базується на багатшій системі, побудованій на JVM). Це дуже хороша бібліотека, щоб додати до ваших проектів Java, щоб написати чистий функціональний код.

Vavr надає Optionмонаду, яка надає функції для роботи з типом Option, наприклад:

  • fold: відобразити значення параметра в обох випадках (визначене / порожнє)
  • onEmpty: дозволяє виконати, Runnableколи параметр порожній
  • peek: дозволяє споживати значення параметра (коли визначено).
  • і це також Serializableнавпаки, Optionalщо означає, що ви можете сміливо використовувати його як аргумент методу та член екземпляра.

Опція відповідає монадним законам, що відрізняються від додаткового "псевдомонади" на Java і забезпечує більш багатий API. І звичайно, ви можете зробити це з додаткового Java (і навпаки): Option.ofOptional(javaOptional)–Vavr орієнтований на сумісність.

Переходимо до прикладу:

// AWESOME Vavr functional collections (immutable for the gread good :)
// fully convertible to Java's counterparts.
final Map<String, String> map = Map("key1", "value1", "key2", "value2");

final Option<String> opt = map.get("nonExistentKey"); // you're safe of null refs!

final String result = opt.fold(
        () -> "Not found!!!",                // Option is None
        val -> "Found the value: " + val     // Option is Some(val)
);

Подальше читання

Нульова довідка, помилка в мільярд доларів

Примітка: Це лише дуже малий приклад того, що пропонує Вавр (відповідність шаблонів, потоки, які називають ледачими списками, монадичні типи, незмінні колекції, ...).


1

Іншим рішенням може бути наступне:

Ось як ви його використовуєте:

    final Opt<String> opt = Opt.of("I'm a cool text");
    opt.ifPresent()
        .apply(s -> System.out.printf("Text is: %s\n", s))
        .elseApply(() -> System.out.println("no text available"));

Або у випадку, якщо у випадку зворотного випадку виправдано:

    final Opt<String> opt = Opt.of("This is the text");
    opt.ifNotPresent()
        .apply(() -> System.out.println("Not present"))
        .elseApply(t -> /*do something here*/);

Це інгредієнти:

  1. Невеликий модифікований функціональний інтерфейс, лише для методу "elseApply"
  2. Необов’язкове вдосконалення
  3. Трохи криву :-)

"Косметично" розширений інтерфейс функцій.

@FunctionalInterface
public interface Fkt<T, R> extends Function<T, R> {

    default R elseApply(final T t) {
        return this.apply(t);
    }

}

І додатковий клас обгортки для вдосконалення:

public class Opt<T> {

    private final Optional<T> optional;

    private Opt(final Optional<T> theOptional) {
        this.optional = theOptional;
    }

    public static <T> Opt<T> of(final T value) {
        return new Opt<>(Optional.of(value));
    }

    public static <T> Opt<T> of(final Optional<T> optional) {
        return new Opt<>(optional);
    }

    public static <T> Opt<T> ofNullable(final T value) {
        return new Opt<>(Optional.ofNullable(value));
    }

    public static <T> Opt<T> empty() {
        return new Opt<>(Optional.empty());
    }

    private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
        if (this.optional.isPresent()) {
            present.accept(this.optional.get());
        } else {
            notPresent.run();
        }
        return null;
    };

   private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
        if (!this.optional.isPresent()) {
            notPresent.run();
        } else {
            present.accept(this.optional.get());
        }
        return null;
    };

    public Fkt<Consumer<T>, Fkt<Runnable, Void>> ifPresent() {
        return Opt.curry(this.ifPresent);
    }

    public Fkt<Runnable, Fkt<Consumer<T>, Void>> ifNotPresent() {
        return Opt.curry(this.ifNotPresent);
    }

    private static <X, Y, Z> Fkt<X, Fkt<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
        return (final X x) -> (final Y y) -> function.apply(x, y);
    }
}

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

Основна ідея тут наступна. У світі нефункціонального стилю програмування ви, ймовірно, реалізуєте метод, що приймає два параметри, де перший - це вид виконуваного коду, який повинен бути виконаний у разі наявності значення, а другий параметр - код, який можна виконати, якщо його слід запустити у випадку значення недоступне. Для кращої читабельності ви можете використовувати curring для розділення функції двох параметрів на дві функції по одному параметру кожна. Це те, що я в основному тут робив.

Підказка: Opt також надає інший випадок використання, коли ви хочете виконати фрагмент коду на випадок, якщо значення недоступне. Це можна зробити також через Optional.filter.stuff, але я вважаю це набагато читабельнішим.

Сподіваюся, що це допомагає!

Гарне програмування :-)


Чи можете ви сказати, що не працює? Я перевірив її знову і для мене це працює?
Алессандро Джуза

Вибачте, але де визначено ckass Fkt? Останнє, але не менш важливе, у мене є проблема компіляції: Помилка: (35, 17) java: змінна опція, можливо, не була ініціалізована
Енріко Джурін

Fkt визначено вище як інтерфейс. Просто прочитайте всю статтю :-)
Алессандро Джуза

Так. Я змінив інтерфейс EFunction на Fkt як ім'я. Був друкарський помилок. Дякую за відгук :-) Вибачте за це.
Алессандро Джуза

Я не думаю, що це гарна ідея писати такий код ... Ви повинні використовувати утиліту jdk, коли зможете.
Тюльпан Тюльпан

0

Якщо ви хочете зберегти значення:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));

0

Припустимо, що у вас є список та уникнення isPresent()проблеми (пов’язаної з необов’язковими), яку ви можете використати, .iterator().hasNext()щоб перевірити, чи немає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.