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.