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

В 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() продолжит выполнение.


person smallufo    schedule 21.05.2014    source источник
comment
имеется в виду orElse для чего-то другого.   -  person Sotirios Delimanolis    schedule 21.05.2014
comment
Да, я знаю . Я просто хочу что-то в этом роде.   -  person smallufo    schedule 21.05.2014
comment
Используйте свой первый образец. Это красиво.   -  person Sotirios Delimanolis    schedule 21.05.2014
comment
Я предлагаю вам прекратить принуждение к определенному поведению при использовании API, который не предназначен для этого поведения. Мне кажется, что ваш первый пример хорош, если не считать некоторых небольших замечаний по поводу стиля, но они вызывают мнение.   -  person skiwi    schedule 21.05.2014
comment
@smallufo: замените return null; на return o; (оба). Однако у меня есть сильное чувство, что вы работаете не в том месте. Вы должны работать на сайте, который выпустил это Optional. В этом месте должен быть способ выполнить желаемую операцию без промежуточного Optional.   -  person Holger    schedule 22.05.2014
comment
Java 9 реализует решение вашей проблемы: iteratrlearning.com/java9/2016 /09/05/java9-optional.html   -  person Pikachu    schedule 16.09.2016
comment
Я думаю, что причина, по которой это нелегко сделать, кроется в преднамеренности. Необязательно должен выполнять не управление потоком, а преобразование значений. Я знаю, что ifPresent противоречит этому. Все остальные методы относятся к значению, а не к действиям.   -  person AlikElzin-kilaka    schedule 20.07.2017
comment
Я бы сказал, что ваш второй пример, в котором используется map, даже менее функциональный, чем версия ifPresent. В функциональном программировании функции не должны вызывать никаких побочных эффектов, таких как печать и изменение других значений. Они могут только делать расчеты. Я избегаю map каждый раз, когда пытаюсь вызвать побочные эффекты. Когда вы видите вызов map, естественно предположить, что побочных эффектов не будет, только преобразование значения из-за его функционального фона. Использование его так, как следует из названия, - гораздо более семантический способ программирования.   -  person Addison    schedule 15.10.2018
comment
Кроме того, я не уверен насчет Java, но на многих языках, таких как Rust и Haskell, map является ленивой операцией, что означает, что на самом деле она не будет вызывать переданный вами блок, пока вы не попытаетесь использовать или проверить значение необязательного . Поэтому, в зависимости от языка, такой оператор может никогда не быть выполнен и в результате будет оптимизирован. Это может не относиться к Java, но это важно знать.   -  person Addison    schedule 15.10.2018
comment
Java любит доводить вас до середины пути, а остальное объединять в кластеры. Очевидно, вы сможете объединить ifPresent(...) в цепочку с orElse(...) или otherwise(...).   -  person Josh M.    schedule 15.08.2019
comment
См. Также Опции цепочки в Java 8   -  person Vadzim    schedule 09.12.2019


Ответы (12)


Для меня ответ @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);

В этом новом коде у вас есть 3 вещи:

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

Кстати, теперь его название более информативное, это на самом деле Consumer ‹Optional‹? ››

person Bassem Reda Zohdy    schedule 01.04.2015
comment
Следует использовать Optional.ofNullable (o) вместо Optional.of (o) - person traeper; 08.03.2017
comment
Вам нужно использовать ofNullable, если вы не уверены, имеет ли значение, которое вы собираетесь использовать, null или нет и не нужно сталкиваться с NPE, и в случае, если вы уверены, что оно не равно null, или вам все равно, если получите NPE. - person Bassem Reda Zohdy; 09.03.2017
comment
Я думаю, что класс OptionalConsumer выглядит лучше, чем if / else в коде. Спасибо! :) - person witek1902; 26.07.2018

Если вы используете Java 9+, вы можете использовать _ 1_ метод:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
person ZhekaKozlov    schedule 08.03.2017
comment
Приятно, потому что он почти такой же чистый, как сопоставление с образцом в Scala - person sscarduzio; 03.11.2017
comment
Две такие лямбды - это довольно уродливо. Я думаю, что if / else намного чище для этих случаев. - person john16384; 18.03.2020
comment
@ john16384 Хорошо, если вы сочтете это некрасивым, я удалю свой ответ (нет). - person ZhekaKozlov; 20.03.2020
comment
Это очень хорошо, но вопрос был предназначен специально для JDK8, потому что ifPresentOrElse недоступен. - person hreinn; 28.05.2020

Java 9 представляет

ifPresentOrElse, если значение присутствует, выполняет заданное действие со значением, в противном случае выполняет заданное действие на основе пустых значений.

См. Отличный Необязательно в шпаргалке по Java 8.

Он предоставляет все ответы для большинства случаев использования.

Краткое резюме ниже

ifPresent () - делать что-нибудь, когда установлен Optional

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);
person Bartosz Bilicki    schedule 27.04.2015
comment
На самом деле это не отвечает на вопрос OP. Он отвечает на множество распространенных применений, но не на то, о чем спрашивал OP. - person Captain Man; 15.05.2018
comment
@CaptainMan действительно есть; выражение opt.map (found) .orElse (not found) заполняет счет. - person Matt; 27.06.2018
comment
@Matt нет, OP специально запрашивает действия, которые он выполняет, когда необязательный параметр присутствует / отсутствует, а не возвращать значение, когда оно есть или нет. OP даже упоминает нечто подобное в вопросе, используя orElseGet, объясняя, почему это не сработает. - person Captain Man; 28.06.2018
comment
@CaptainMan Я понимаю вашу точку зрения. Я действительно думаю, что он мог бы заставить его работать, если бы он не возвращал null из map, но немного странно просить функциональное решение, чтобы вы могли вызвать DAO. Мне кажется, было бы разумнее вернуть обновленный / новый объект из этого блока map.orElse, а затем сделать то, что вам нужно сделать с возвращенным объектом. - person Matt; 28.06.2018
comment
Я думаю, map сосредоточен на самом потоке и не предназначен для выполнения действий с другим объектом в зависимости от статуса этого элемента в потоке. Приятно знать, что ifPresentOrElse добавлен в Java 9. - person WesternGun; 01.10.2018
comment
Без java 9, в котором есть ifPresentOrElse(), то, что хочет op, невозможно выполнить функционально, и здесь ничего не помогает. - person Kalec; 17.10.2019

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

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

Я не думаю, что это улучшает читаемость.

Или, как предложил Марко, используйте тернарный оператор:

System.out.println(opt.isPresent() ? "Found" : "Not found");
person assylias    schedule 21.05.2014
comment
Спасибо @assylias, но я не думаю, что Optional.map () работает в этом случае (см. Мое обновление контекста). - person smallufo; 21.05.2014
comment
@smallufo Вам нужно будет вернуть new Object(); в вашей первой лямбде, но, честно говоря, это становится очень некрасиво. Я бы придерживался if / else для вашего обновленного примера. - person assylias; 21.05.2014
comment
Согласитесь, использование map для простого возврата Optional для связывания затрудняет понимание кода, в то время как map предполагается буквально отображать что-то. - person Tiina; 12.01.2019

Другое решение - использовать функции высшего порядка следующим образом

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
person user5057016    schedule 27.06.2015
comment
На мой взгляд, лучшее решение без JDK 9. - person Semaphor; 11.02.2016
comment
Объяснение было бы отличным. Я спрашиваю себя, почему вам нужно использовать работающую карту (?) И что означает часть value -> () -> syso. - person froehli; 22.05.2017
comment
Спасибо за это решение! Я думаю, что причина использования Runnable заключается в том, что out map не возвращает никакого значения, а с Runnable он возвращает лямбда, и, например, результат карты - это лямбда, после которой мы ее запускаем. Итак, если у вас есть возвращаемое значение, вы можете использовать следующее: String result = opt.map(value -> "withOptional").orElse("without optional"); - person nanotexnik; 01.11.2017

Нет отличного способа сделать это из коробки. Если вы хотите использовать свой более чистый синтаксис на регулярной основе, вы можете создать вспомогательный класс, который поможет:

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"));
person Dane White    schedule 21.05.2014
comment
Спасибо. Это прекрасное решение. Я знаю, что, возможно, нет встроенного решения (если JDK не включает такой метод). Вам OptionalEx очень поможет. В любом случае, благодарю Вас. - person smallufo; 22.05.2014
comment
Да, результат мне нравится, и стиль его поддерживает. ТАК, а почему бы не в стандартном API? - person guthrie; 19.11.2014
comment
Хороший ответ. Мы делаем то же самое. Я согласен, что он должен быть в API (или языке!), Но он был отклонен: bugs.openjdk.java.net/browse/JDK-8057557. - person Garrett Smith; 30.01.2015
comment
Отлично. Это должно быть частью JDK 8.1 для рассмотрения. - person peter_pilgrim; 02.04.2015
comment
Optional.ifPresentOrElse() был добавлен в JDK 9. - person Stuart Marks; 03.04.2015
comment
@peter_pilgrim Невозможно вносить изменения в API в выпусках обновлений, но, по крайней мере, это уже готово. - person Stuart Marks; 03.04.2015

Если вы можете использовать только Java 8 или ниже:

1) если у вас нет spring-data, лучший способ:

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

Теперь я знаю, что это не так читаемо и даже трудно понять кому-то, но лично мне кажется, что это нормально, и я не вижу другого удобного способа для этого случая.

2) если вам повезло и вы можете использовать spring-data, лучший способ - это Optionals # 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"));
person Tyulpan Tyulpan    schedule 14.05.2019

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

Vavr предоставляет монаду Option, которая предоставляет функции для работы с типом Option, такие как:

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

Option следует законам монад в отличие от псевдомонады Optional в Java и предоставляет более богатый API. И, конечно, вы можете сделать это с помощью Java Optional (и наоборот): 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)
);

Более того, все типы Vavr могут быть преобразованы в его аналоги на Java, для примера: Optional javaOptional = opt.toJava(), очень просто :) Конечно, преобразование существует и другим способом: Option option = Option.ofOptional(javaOptional).

N.B. Вавр предлагает io.vavr.API класс с множеством удобных статических методов =)

дальнейшее чтение

Пустая ссылка, ошибка на миллиард долларов

N.B. Это лишь очень маленький пример того, что предлагает Vavr (сопоставление с образцом, потоки, также известные как ленивые вычисляемые списки, монадические типы, неизменяемые коллекции, ...).

person Gerard Bosch    schedule 27.04.2019

Другое решение может быть следующим:

Вот как вы его используете:

    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, но я нашел его более читаемым.

Надеюсь, это поможет!

Хорошее программирование :-)

person Alessandro Giusa    schedule 09.07.2016
comment
Вы можете сказать, что не работает? Я протестировал еще раз, и у меня все работает? - person Alessandro Giusa; 11.11.2017
comment
Простите, а где определяется ckass Fkt? И последнее, но не менее важное: у меня проблема с компиляцией: Ошибка: (35, 17) java: необязательная переменная могла не быть инициализирована - person Enrico Giurin; 12.11.2017
comment
Fkt определен выше как интерфейс. Просто прочтите всю статью :-) - person Alessandro Giusa; 19.11.2017
comment
да. Я изменил интерфейс EFunction на Fkt в качестве имени. Произошла опечатка. Спасибо за обзор :-) извините за это. - person Alessandro Giusa; 19.11.2017
comment
Я не думаю, что это хорошая идея писать такой код ... Вам следует использовать утилиту jdk, когда это возможно. - person Tyulpan Tyulpan; 30.05.2019

Вы не можете вызвать orElse после ifPresent, причина в том, что orElse вызывается по дополнительному запросу, но ifPresent возвращает значение void. Так что лучший подход к достижению - ifPresentOrElse. Это могло быть так:

op.ifPresentOrElse( 
            (value) 
                -> { System.out.println( 
                         "Value is present, its: "
                         + value); }, 
            () 
                -> { System.out.println( 
                         "Value is empty"); }); 
person M.Khan    schedule 25.07.2020

Если вы хотите сохранить значение:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));
person Jhutan Debnath    schedule 17.07.2018
comment
не думаю, что эта пара - лучшее решение. по этой причине вы можете создать несколько POJO. С Java 15 он мог использовать записи. - person catch23; 06.01.2021

Предположим, что у вас есть список, и вы избежите проблемы isPresent() (связанной с дополнительными параметрами), вы можете использовать .iterator().hasNext(), чтобы проверить, отсутствует ли он.

person Leandro Maro    schedule 21.03.2019