Java 8 - różnica między Optional.flatMap i Optional.map

162

Jaka jest różnica między tymi dwiema metodami: Optional.flatMap()i Optional.map()?

Przykład byłby mile widziany.

współzależny
źródło
5
@AlexisC. Twój link dotyczy mapy strumienia i płaskiej mapy, a nie opcjonalnej.
Eran
1
@Eran To nie ma znaczenia, jeśli rozumiesz, jak działa map / flatMap, niezależnie od tego, czy jest to strumień, czy nie, to jest to samo dla elementu opcjonalnego. Jeśli operator zrozumiał, jak to działa w przypadku Strumienia, nie powinien zadawać tego pytania. Koncepcja jest taka sama.
Alexis C.
2
@AlexisC. Nie całkiem. FlatMap Optional ma niewiele wspólnego z flatMap Stream.
Eran
1
@Eran Mówię o różnicy koncepcyjnej między mapą a płaską mapą, nie tworzę korespondencji jeden do jednego między Stream#flatMapi Optional#flatMap.
Alexis C.

Odpowiedzi:

166

Użyj, mapjeśli funkcja zwraca potrzebny obiekt lub flatMapjeśli funkcja zwraca Optional. Na przykład:

public static void main(String[] args) {
  Optional<String> s = Optional.of("input");
  System.out.println(s.map(Test::getOutput));
  System.out.println(s.flatMap(Test::getOutputOpt));
}

static String getOutput(String input) {
  return input == null ? null : "output for " + input;
}

static Optional<String> getOutputOpt(String input) {
  return input == null ? Optional.empty() : Optional.of("output for " + input);
}

Obie instrukcje print drukują to samo.

asylias
źródło
5
Pytanie: czy [flat]Mapkiedykolwiek wywołałby funkcję mapowania z input == null? Rozumiem, że Optionalskróty sortowania, jeśli ich nie ma - [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ) wydaje się potwierdzać to - „ Jeśli wartość jest obecna, zastosuj .. . ”.
Borys Pająk
1
@BoristheSpider Optional.of (null)! = Optional.empty ()
Diego Martinoia
14
@DiegoMartinoia Optional.of(null)jest Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider
1
@BoristheSpider tak, masz rację. Próbowałem odpowiedzieć na twoje pytanie, ale myślę, że uczyniłem to jeszcze bardziej niejasnym: koncepcyjnie, Opcjonalnie.ofNullable (null) NIE powinno być puste, ale w praktyce jest uważane za takie, dlatego mapa / mapa nie jest wykonywana.
Diego Martinoia
1
Myślę, że wejście nie powinno być puste albo getOutputOpt lub getOutput
DanyalBurke
55

Obaj przejmują funkcję od typu opcjonalnego do czegoś.

map()stosuje funkcję „ tak jak jest ” do posiadanej opcji:

if (optional.isEmpty()) return Optional.empty();
else return Optional.of(f(optional.get()));

Co się stanie, jeśli twoja funkcja jest funkcją z T -> Optional<U>?
Twój wynik to teraz Optional<Optional<U>>!

O to flatMap()właśnie chodzi: jeśli twoja funkcja już zwraca an Optional, flatMap()jest trochę mądrzejsza i nie zawija go podwójnie, zwraca Optional<U>.

To kompozycja dwóch funkcjonalnych idiomów: mapi flatten.

Diego Martinoia
źródło
7

Uwaga: - poniżej znajduje się ilustracja mapy i funkcji mapy płaskiej, w przeciwnym razie Opcjonalne jest przeznaczone głównie do użytku wyłącznie jako typ zwracany.

Jak już być może wiesz, Optional to rodzaj kontenera, który może zawierać pojedynczy obiekt lub nie, więc może być używany wszędzie tam, gdzie spodziewasz się wartości null (możesz nigdy nie zobaczyć NPE, jeśli użyjesz opcji Optional prawidłowo). Na przykład, jeśli masz metodę, która oczekuje obiektu osoby, który może mieć wartość null, możesz napisać metodę mniej więcej tak:

void doSome(Optional<Person> person){
  /*and here you want to retrieve some property phone out of person
    you may write something like this:
  */
  Optional<String> phone = person.map((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}
class Person{
  private String phone;
  //setter, getters
}

Tutaj zwróciłeś typ String, który jest automatycznie zawijany w typ opcjonalny.

Jeśli klasa osoby wyglądała tak, to znaczy telefon jest również opcjonalny

class Person{
  private Optional<String> phone;
  //setter,getter
}

W tym przypadku wywołanie funkcji map spowoduje zawinięcie zwróconej wartości w Optional i da coś takiego:

Optional<Optional<String>> 
//And you may want Optional<String> instead, here comes flatMap

void doSome(Optional<Person> person){
  Optional<String> phone = person.flatMap((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}

PS; Nigdy nie wywołuj metody get (jeśli musisz) na Optional bez sprawdzania jej za pomocą isPresent (), chyba że nie możesz żyć bez NullPointerExceptions.

SandeepGodara
źródło
1
Myślę, że ten przykład może odwrócić uwagę od natury twojej odpowiedzi, ponieważ twoja klasa Personjest nadużywana Optional. Używanie API Optionalna takich członkach jest wbrew intencjom API - patrz mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/ ...
8bitjunkie
@ 8bitjunkie Dzięki za zwrócenie uwagi, różni się od opcji Scali ..
SandeepGodara
6

Pomogło mi przyjrzenie się kodowi źródłowemu obu funkcji.

Mapa - zawija wynik w opcjonalny.

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value)); //<--- wraps in an optional
    }
}

flatMap - zwraca obiekt „surowy”

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value)); //<---  returns 'raw' object
    }
}
Robert Niestroj
źródło
1
Co masz na myśli, mówiąc flatMap„zwraca„ surowy ”obiekt”? flatMapzwraca również mapowany obiekt „opakowany” w plik Optional. Różnica polega na tym, że w przypadku flatMapfunkcji mapper opakowuje mapowany obiekt, Optionalpodczas gdy mapsama zawija obiekt Optional.
Derek Mahar
@DerekMahar usunął mój, nie musisz go ponownie publikować, ponieważ poprawnie zredagowałeś swój komentarz.
maxxyme
3
  • Optional.map():

Pobiera każdy element i jeśli wartość istnieje, jest przekazywana do funkcji:

Optional<T> optionalValue = ...;
Optional<Boolean> added = optionalValue.map(results::add);

Teraz dodane ma jedną z trzech wartości: truelub falsezawinięte w opcjonalne , jeśli optionalValuebyło obecne, lub puste opcjonalne, w przeciwnym razie.

Jeśli nie musisz przetwarzać wyniku, którego możesz po prostu użyć ifPresent(), nie ma on wartości zwracanej:

optionalValue.ifPresent(results::add); 
  • Optional.flatMap():

Działa podobnie do tej samej metody strumieni. Spłaszcza strumień strumieni. Z tą różnicą, że jeśli wartość jest prezentowana, jest stosowana do funkcji. W przeciwnym razie zwracany jest pusty element opcjonalny.

Możesz go użyć do tworzenia opcjonalnych wywołań funkcji wartości.

Załóżmy, że mamy metody:

public static Optional<Double> inverse(Double x) {
    return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

public static Optional<Double> squareRoot(Double x) {
    return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}

Następnie możesz obliczyć pierwiastek kwadratowy z odwrotności, na przykład:

Optional<Double> result = inverse(-4.0).flatMap(MyMath::squareRoot);

lub jeśli wolisz:

Optional<Double> result = Optional.of(-4.0).flatMap(MyMath::inverse).flatMap(MyMath::squareRoot);

Jeśli zwraca inverse()lub , wynik jest pusty.squareRoot()Optional.empty()

nazar_art
źródło
1
To się nie kompiluje. Oba twoje wyrażenia zwracają opcjonalne <Double>, a nie Double, do którego przypisujesz wynik.
JL_SO,
@JL_SO masz rację. Ponieważ inverse ma Optional<Double>typ jako typ zwracany.
nazar_art
3

W porządku. Musisz używać „flatMap” tylko wtedy, gdy masz do czynienia z zagnieżdżonymi opcjami . Oto przykład.

public class Person {

    private Optional<Car> optionalCar;

    public Optional<Car> getOptionalCar() {
        return optionalCar;
    }
}

public class Car {

    private Optional<Insurance> optionalInsurance;

    public Optional<Insurance> getOptionalInsurance() {
        return optionalInsurance;
    }
}

public class Insurance {

    private String name;

    public String getName() {
        return name;
    }

}

public class Test {

    // map cannot deal with nested Optionals
    public Optional<String> getCarInsuranceName(Person person) {
        return person.getOptionalCar()
                .map(Car::getOptionalInsurance) // ① leads to a Optional<Optional<Insurance>
                .map(Insurance::getName);       // ②
    }

}

Podobnie jak w przypadku Stream, opcjonalna # map zwróci wartość opakowaną przez opcjonalny. Dlatego otrzymujemy zagnieżdżony plik Optional - Optional<Optional<Insurance>. W ② chcemy odwzorować to jako instancję ubezpieczeniową, tak właśnie doszło do tragedii. Katalog główny jest zagnieżdżony Optionals. Jeśli uda nam się uzyskać podstawową wartość niezależnie od powłok, zrobimy to. To właśnie robi flatMap.

public Optional<String> getCarInsuranceName(Person person) {
    return person.getOptionalCar()
                 .flatMap(Car::getOptionalInsurance)
                 .map(Insurance::getName);
}

W końcu zdecydowanie poleciłem ci Java 8 w działaniu , jeśli chcesz systematycznie uczyć się Java8.

momonannan
źródło