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ć.


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


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

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

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

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

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

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

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, ...).


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


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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.