Функциональный стиль Java 8 Optional.ifPresent и 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()продолжим исполнять.

smallufo
источник
53
Используйте свой первый образец. Это красиво .
Сотириос Делиманолис
3
Я предлагаю вам прекратить форсировать определенное поведение при использовании API, который не предназначен для этого поведения. Ваш первый пример выглядит хорошо для меня, за исключением некоторых небольших замечаний по стилю, но они заслуживают внимания.
Скиви
4
@smallufo: заменить return null;на return o;(оба). Однако у меня есть сильное чувство, что вы работаете не в том месте. Вы должны работать на сайте, который произвел это Optional. В этом месте должен быть способ выполнить желаемую операцию без промежуточного звена Optional.
Хольгер
10
Java 9 реализует решение вашей проблемы: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk
2
Я думаю, что причина этого не может быть легко сделана специально. Необязательно не должен делать управление потоком, а скорее преобразование значения. Я знаю, что ifPresentпротиворечит этому. Все остальные методы относятся к значению, а не к действиям.
Алик Эльзин-килака

Ответы:

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);

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

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

кстати, теперь его название более наглядно, это на самом деле Consumer>

Bassem Reda Zohdy
источник
3
Следует использовать Optional.ofNullable (o) вместо Optional.of (o)
traeper
2
Вам нужно использовать ofNullable, если вы не уверены, что значение, которое вы собираетесь использовать, имеют значение NULL или нет, и вам не нужно сталкиваться с NPE, а также в случае, если вы уверены, что оно не равно NULL, или вам все равно, получите NPE.
Bassem Reda Zohdy
1
Я думаю, что класс OptionalConsumer выглядит лучше, чем if / else в коде. Спасибо! :)
witek1902
207

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

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
ZhekaKozlov
источник
3
Хорошо, потому что это почти так же чисто, как сопоставление с образцом в Scala
sscarduzio
Две лямбды вот такие уродливые. Я думаю, если / еще намного чище для этих случаев.
Джон16384
1
@ john16384 Хорошо, если ты находишь это уродливым, тогда я удалю свой ответ (нет).
Жека Козлов
Это очень хорошо, но вопрос был задан специально для JDK8, потому что ifPresentOrElse недоступен.
hreinn
81

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);
Бартош Билицкий
источник
66
Это на самом деле не отвечает на вопрос ОП. Он отвечает на многие общие вопросы, но не на то, что спросил OP.
Капитан Мэн
1
@CaptainMan на самом деле это делает; выражение opt.map («найдено»). orElse («не найдено») заполняет счет.
Мэтт
4
@Matt нет, OP специально запрашивает действия, которые он выполнял, когда необязательный параметр присутствует / отсутствует, чтобы не возвращать значение, если оно есть или нет. OP даже упоминает нечто подобное в вопросе, используя orElseGet, объясняя, почему это не сработает.
Капитан Мэн
2
@CaptainMan Я понимаю вашу точку зрения. Я действительно думаю, что он мог бы заставить его работать, если бы он не возвратил null из 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");
assylias
источник
2
Спасибо @assylias, но я не думаю, что Optional.map () работает для этого случая (см. Мое обновление контекста).
smallufo
2
@smallufo Вам нужно будет вернуться new Object();в первую лямбду, но, честно говоря, это становится очень уродливо. Я бы придерживался if / else для вашего обновленного примера.
assylias
Согласитесь, использование mapпростого возврата Optionalдля создания цепочки усложняет понимание кода, в то время mapкак предполагается, что оно буквально отображается на что-то.
Тийна
40

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

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
user5057016
источник
8
На мой взгляд, лучшее решение до сих пор без JDK 9.
Semaphor
5
Объяснение было бы здорово. Я спрашиваю себя, почему вы должны использовать работоспособную карту (?) И что value -> () -> sysoозначает эта часть.
froehli
Спасибо за это решение! Я думаю, что причина использования Runnable заключается в том, что out map не возвращает никакого значения, а с Runnable возвращает lambda, и, таким образом, результатом map является lambda, мы запускаем его после. Поэтому, если у вас есть возвращаемое значение, вы можете использовать следующее: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?
Гатри
Хороший ответ. Мы делаем то же самое. Я согласен, что это должно быть в API (или языке!), Но оно было отклонено: bugs.openjdk.java.net/browse/JDK-8057557 .
Гарретт Смит
Ницца. Это должно быть частью JDK 8.1 для рассмотрения.
peter_pilgrim
35
Optional.ifPresentOrElse()был добавлен в JDK 9.
Стюарт Маркс
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лучший способ, это 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"));
Тюльпан Тюльпан
источник
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)
);

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

Нулевая ссылка, ошибка в миллиард долларов

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

Джерард Бош
источник
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. Немного измененный интерфейс Function, только для метода 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, но я нашел это гораздо более читабельным.

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

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

Алессандро Джуза
источник
Можете ли вы сказать, что не работает? Я проверил это снова и для меня это работает?
Алессандро Джуза
Извините, а где определяется ф-кака Fkt? И последнее, но не менее важное: у меня проблема с компиляцией: Ошибка: (35, 17) Java: необязательная переменная, возможно, не была инициализирована
Enrico Giurin
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()чтобы проверить, если нет.

Леандро Маро
источник