Funkcjonalny styl Java 8's Optional.ifPresent i if-not-Present?

274

W Javie 8 chcę zrobić coś z Optionalobiektem, jeśli jest obecny, i zrobić coś innego, jeśli go nie ma.

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

Nie jest to jednak „funkcjonalny styl”.

Optionalma ifPresent()metodę, ale nie mogę połączyć orElse()metody.

Dlatego nie mogę napisać:

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

W odpowiedzi na @assylias nie sądzę, że Optional.map()działa w następującym przypadku:

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

W takim przypadku, gdy optjest obecny, aktualizuję jego właściwość i zapisuję w bazie danych. Kiedy nie jest dostępny, tworzę nowy obji zapisuję w bazie danych.

Uwaga w dwóch lambdach muszę wrócić null.

Ale kiedy optbędzie obecny, oba lambdas zostaną stracone. objzostanie zaktualizowany, a nowy obiekt zostanie zapisany w bazie danych. Wynika to z return nullpierwszej lambda. I orElseGet()będzie nadal działać.

smallufo
źródło
53
Użyj pierwszej próbki. To jest piękne .
Sotirios Delimanolis
3
Sugeruję, abyś przestał wymuszać określone zachowanie, gdy używasz interfejsu API, który nie jest przeznaczony dla tego zachowania. Twój pierwszy przykład wygląda mi dobrze poza kilkoma drobnymi uwagami w stylu, ale są one opiniowane.
skiwi
4
@smallufo zamienić return null;z return o;(oba). Mam jednak silne przeczucie, że pracujesz w niewłaściwym miejscu. Powinieneś pracować w witrynie, która go wyprodukowała Optional. W tym miejscu powinien istnieć sposób wykonania żądanej operacji bez pośrednika Optional.
Holger
10
Java 9 implementuje rozwiązanie Twojego problemu: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk
2
Myślę, że powodem, dla którego nie można tego łatwo zrobić, jest celowy. Opcjonalnie nie należy wykonywać kontroli przepływu, a raczej transformację wartości. Wiem, że ifPresentzaprzecza to. Wszystkie inne metody odnoszą się do wartości, a nie do akcji.
AlikElzin-kilaka

Odpowiedzi:

109

Dla mnie odpowiedź @Dane White jest OK, po pierwsze nie lubiłem używać Runnable, ale nie mogłem znaleźć żadnych alternatyw, tutaj kolejna implementacja wolała więcej

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

Następnie :

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

Aktualizacja 1:

powyższe rozwiązanie dla tradycyjnego sposobu rozwoju, gdy masz wartość i chcesz ją przetworzyć, ale co jeśli chcę zdefiniować funkcjonalność i wykonanie, to sprawdź poniższe ulepszenie;

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

Następnie może być użyty jako:

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

W tym nowym kodzie masz 3 rzeczy:

  1. potrafi łatwo zdefiniować funkcjonalność, zanim będzie istniał obiekt.
  2. nie tworząc odwołania do obiektu dla każdego Opcjonalnego, tylko jednego, masz mniej pamięci niż GC.
  3. wdraża konsumenta w celu lepszego wykorzystania z innymi komponentami.

tak na marginesie, teraz jego nazwa jest bardziej opisowa, tak naprawdę jest Konsumentem

Bassem Reda Zohdy
źródło
3
Należy użyć Optional.ofNullable (o) zamiast Optional.of (o)
traeper
2
Musisz użyć parametru Nullable, jeśli nie masz pewności, czy wartość, której zamierzasz użyć, ma wartość zerową, czy nie i nie musisz stawić czoła NPE, a jeśli masz pewność, że nie jest ona zerowa lub nie obchodzi Cię, czy otrzymasz NPE.
Bassem Reda Zohdy,
1
Myślę, że klasa OptionalConsumer wygląda lepiej niż jeśli / w kodzie. Dzięki! :)
witek1902
207

Jeśli używasz Java 9+, możesz użyć ifPresentOrElse()metody:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
ZhekaKozlov
źródło
3
Fajnie, ponieważ jest prawie tak czysty jak dopasowywanie wzorów w Scali
sscarduzio
Takie dwa lambda są dość brzydkie. Myślę, że jeśli / else jest znacznie czystszy w tych przypadkach.
john16384
1
@ john16384 OK, jeśli uznasz to za brzydkie, usunę odpowiedź (nie).
ZhekaKozlov
To bardzo miłe, ale pytanie było przeznaczone specjalnie dla JDK8, ponieważ ifPresentOrElse nie jest dostępne.
Hreinn
81

Java 9 wprowadza

ifPresentOrElse, jeśli wartość jest obecna, wykonuje daną akcję z wartością, w przeciwnym razie wykonuje daną akcję pustą.

Zobacz doskonały Opcjonalny w ściągawce Java 8 .

Zawiera wszystkie odpowiedzi dla większości przypadków użycia.

Krótkie podsumowanie poniżej

ifPresent () - zrób coś, gdy opcja jest ustawiona

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

filter () - odrzuć (odfiltruj) pewne wartości opcjonalne.

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

map () - transformuj wartość, jeśli jest obecna

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

orElse () / orElseGet () - zmieniając pusty Opcjonalnie na domyślny T.

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

orElseThrow () - leniwie rzuca wyjątki na puste Opcjonalne

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);
Bartosz Bilicki
źródło
66
To tak naprawdę nie odpowiada na pytanie OP. Odpowiada na wiele typowych zastosowań, ale nie na pytania OP.
Captain Man
1
@CaptainMan faktycznie to robi; wyrażenie opt.map („znaleziono”). orElse („nie znaleziono”) wypełnia rachunek.
Matt
4
@Matt nie, OP konkretnie prosi o działania, które wykona, gdy opcja nie jest / nie jest obecna, aby nie zwracała wartości, gdy jest lub nie. OP wspomina nawet o czymś podobnym w pytaniu za pomocą orElseGet wyjaśniając, dlaczego to nie zadziała.
Kapitan Man
2
@CaptainMan Widzę twój punkt widzenia. Myślę, że mógłby sprawić, by działał, gdyby nie zwrócił wartości null od map, ale trochę dziwne jest prosić o funkcjonalne rozwiązanie, abyś mógł zadzwonić do DAO. Wydaje mi się, że sensowniej byłoby zwrócić zaktualizowany / nowy obiekt z tego map.orElsebloku, a następnie zrobić to, co trzeba zrobić ze zwróconym obiektem.
Matt
1
Myślę, że mapskupiam się na samym strumieniu i nie jest on przeznaczony do „robienia rzeczy z innym obiektem w zależności od statusu tego elementu w strumieniu”. Dobrze wiedzieć, że ifPresentOrElsezostał dodany w Javie 9.
WesternGun
53

Alternatywą jest:

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

Nie sądzę jednak, żeby poprawiało to czytelność.

Lub, jak sugerował Marko, użyj operatora trójskładnikowego:

System.out.println(opt.isPresent() ? "Found" : "Not found");
assylias
źródło
2
Dzięki @assylias, ale nie sądzę, że Optional.map () działa w przypadku (zobacz moją aktualizację kontekstu).
smallufo
2
@smallufo Musisz wrócić new Object();w pierwszej lambda, ale szczerze mówiąc, staje się to bardzo brzydkie. Chciałbym trzymać się if / else dla twojego zaktualizowanego przykładu.
assylias
Zgadzam się, użycie mappo prostu powrotu Optionaldo łańcucha powoduje, że kod jest trudniejszy do zrozumienia, podczas gdy mapzakłada się, że dosłownie mapuje na coś.
Tiina
40

Innym rozwiązaniem byłoby użycie funkcji wyższego rzędu w następujący sposób

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
użytkownik5057016
źródło
8
Moim zdaniem najlepsze do tej pory rozwiązanie bez JDK 9.
Semaphor
5
Wyjaśnienie byłoby świetne. Zadaję sobie pytanie, dlaczego musisz użyć uruchamialnej mapy (?) I co value -> () -> sysooznacza ta część.
froehli
Dzięki za to rozwiązanie! Myślę, że powodem użycia Runnable jest to, że nasza mapa nie zwraca żadnej wartości, a Runnable zwraca lambda, a na przykład wynikiem mapy jest lambda, po której ją uruchamiamy. Jeśli więc masz wartość String result = opt.map(value -> "withOptional").orElse("without optional");
zwracaną
21

Nie ma świetnego sposobu na zrobienie tego po wyjęciu z pudełka. Jeśli chcesz regularnie używać czystszej składni, możesz utworzyć klasę narzędzi, która pomoże:

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

Następnie możesz użyć importu statycznego w innym miejscu, aby uzyskać składnię zbliżoną do tego, czego szukasz:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));
Dane White
źródło
Dzięki. To rozwiązanie jest piękne. Wiem, że może nie ma wbudowanego rozwiązania (chyba że JDK zawiera taką metodę). Ty OptionalEx jest bardzo pomocny. Mimo wszystko dziękuję.
smallufo
Tak, podoba mi się wynik i styl, który obsługuje. Więc dlaczego nie w standardowym API?
guthrie,
Dobra odpowiedź. Robimy to samo. Zgadzam się, że powinien być w interfejsie API (lub języku!), Ale został odrzucony: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith
Miły. To powinno być częścią JDK 8.1 do rozważenia.
peter_pilgrim
35
Optional.ifPresentOrElse()został dodany do JDK 9.
Stuart Marks
9

Jeśli możesz używać tylko Java 8 lub starszej wersji:

1) jeśli nie masz spring-datajak dotąd najlepszego sposobu to:

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

Teraz wiem, że nie jest to dla kogoś tak czytelne, a nawet trudne do zrozumienia, ale osobiście wygląda dobrze dla mnie i nie widzę innego płynnego sposobu na tę sprawę.

2) jeśli masz szczęście i możesz użyć spring-data najlepszego sposobu, to Optionals # ifPresentOrElse :

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

Jeśli możesz korzystać z Java 9, zdecydowanie powinieneś skorzystać z:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));
Tyulpan Tyulpan
źródło
2

Opisane zachowanie można osiągnąć za pomocą Vavr (wcześniej znanej jako JavaSlang), obiektowo-bibliotecznej biblioteki dla Java 8+, która implementuje większość konstrukcji Scali (będąc Scalą bardziej ekspresyjnym językiem z bogatszym systemem typów zbudowanym na JVM). Jest to bardzo dobra biblioteka do dodawania do projektów Java w celu pisania czystego kodu funkcjonalnego.

Vavr zapewnia monadę, Optionktóra udostępnia funkcje do pracy z typem opcji, takie jak:

  • fold: aby zmapować wartość opcji na oba przypadki (zdefiniowane / puste)
  • onEmpty: pozwala wykonać, Runnablegdy opcja jest pusta
  • peek: pozwala zużyć wartość opcji (jeśli jest zdefiniowana).
  • i Serializableprzeciwnie, Optionalco oznacza, że ​​możesz bezpiecznie używać go jako argumentu metody i elementu instancji.

Opcja jest zgodna z prawami monady w odróżnieniu od Opcjonalnej „pseudo-monady” Javy i zapewnia bogatszy interfejs API. I oczywiście możesz to zrobić z Opcjonalnej Javy (i na odwrót): Option.ofOptional(javaOptional)–Vavr koncentruje się na interoperacyjności.

Przechodząc do przykładu:

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

Dalsza lektura

Brak odniesienia, błąd miliarda dolarów

Uwaga: To tylko bardzo mały przykład tego, co oferuje Vavr (dopasowanie wzorca, strumienie zwane leniwie ocenionymi listami, typy monadyczne, niezmienne zbiory, ...).

Gerard Bosch
źródło
1

Innym rozwiązaniem może być:

Oto jak go używasz:

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

Lub w przypadku odwrotnego przypadku użycia:

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

Oto składniki:

  1. Mało zmodyfikowany interfejs funkcji, tylko dla metody „elseApply”
  2. Opcjonalne ulepszenie
  3. Trochę curringu :-)

„Kosmetycznie” ulepszony interfejs funkcji.

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

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

}

I opcjonalna klasa otoki dla ulepszeń:

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

To powinno załatwić sprawę i może służyć jako podstawowy szablon radzenia sobie z takimi wymaganiami.

Podstawowa idea tutaj jest następująca. W świecie programowania niefunkcjonalnego prawdopodobnie zaimplementowałbyś metodę przyjmującą dwa parametry, przy czym pierwszy to rodzaj kodu uruchamialnego, który powinien zostać wykonany w przypadku, gdy wartość jest dostępna, a drugim parametrem jest kod uruchamialny, który należy uruchomić w przypadku wartość nie jest dostępna. Ze względu na lepszą czytelność można użyć curlingu, aby podzielić funkcję dwóch parametrów na dwie funkcje po jednym parametrze. Tak właśnie zrobiłem tutaj.

Wskazówka: Opt zapewnia także inny przypadek użycia, w którym chcesz wykonać fragment kodu na wypadek, gdyby wartość nie była dostępna. Można to zrobić również za pośrednictwem Optional.filter.stuff, ale uważam, że jest to o wiele bardziej czytelne.

Mam nadzieję, że to pomaga!

Dobre programowanie :-)

Alessandro Giusa
źródło
Czy możesz powiedzieć, co nie działa? Przetestowałem to jeszcze raz i dla mnie działa?
Alessandro Giusa,
Przepraszam, ale gdzie zdefiniowano ckass Fkt? Na
koniec
Fkt jest zdefiniowany powyżej jako interfejs. Przeczytaj cały artykuł :-)
Alessandro Giusa,
Tak. Zmieniłem interfejs EFunction na Fkt jako nazwę. Była literówka. Dzięki za recenzję :-) przepraszam za to.
Alessandro Giusa,
Nie sądzę, że to dobry pomysł na pisanie takiego kodu ... Powinieneś użyć narzędzia jdk, kiedy możesz.
Tyulpan Tyulpan
0

Jeśli chcesz zapisać wartość:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));
Jhutan Debnath
źródło
0

Załóżmy, że masz listę i unikniesz isPresent()problemu (związanego z opcjami), którego możesz użyć, .iterator().hasNext()aby sprawdzić, czy nie jest obecny.

Leandro Maro
źródło