Java 8 - Разница между Optional.flatMap и Optional.map

162

В чем разница между этими двумя методами: Optional.flatMap()а Optional.map()?

Пример будет оценен.

созависимых
источник
1
См. Stackoverflow.com/questions/26684562/…
Алексис С.
5
@AlexisC. Ваша ссылка о карте Stream и flatMap, а не как опция.
Eran
1
@Eran Это не имеет значения, если вы понимаете, как работает map / flatMap, для потока это или нет, то же самое для Optional. Если операция поняла, как это работает для Stream, ему не следует задавать этот вопрос. Концепция такая же.
Алексис С.
2
@AlexisC. На самом деле, нет. FlatMap от Optional имеет мало общего с flatMap от Stream.
Eran
1
@Eran Я говорю о концептуальной разнице между картой и flatMap, я не делаю однозначное соответствие между Stream#flatMapи Optional#flatMap.
Алексис С.

Ответы:

166

Используйте, mapесли функция возвращает нужный вам объект или flatMapесли функция возвращает Optional. Например:

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

Оба оператора печати печатают одно и то же.

assylias
источник
5
Вопрос: будет ли [flat]Mapкогда-либо вызывать функцию отображения с input == null? OptionalНасколько я понимаю, сортировка, если она отсутствует, [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ), кажется, подтверждает это: « Если значение присутствует, примените .. . "
Борис Паук
1
@BoristheSpider Optional.of (null)! = Optional.empty ()
Диего Мартиноя
14
@DiegoMartinoia Optional.of(null)это Exception. Optional.ofNullable(null) == Optional.empty(),
Борис Паук
1
@BoristheSpider да, вы правы. Я пытался ответить на ваш вопрос, но думаю, что сделал его еще более неясным: концептуально Optional.ofNullable (null) НЕ должен быть пустым, но на практике он считается, и поэтому map / flatmap не выполняются.
Диего Мартиноя
1
Я думаю, что входные данные никогда не должны быть нулевыми ни в getOutputOpt, ни в getOutput
DanyalBurke
55

Они оба принимают функцию от необязательного типа к чему-то.

map()применяет функцию « как есть » к необязательному:

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

Что произойдет, если ваша функция является функцией от T -> Optional<U>?
Ваш результат теперь Optional<Optional<U>>!

Вот что flatMap()значит: если ваша функция уже возвращает an Optional, flatMap()она немного умнее и не переносит ее дважды, возвращая Optional<U>.

Это композиция из двух функциональных идиом: mapи flatten.

Диего Мартинойя
источник
7

Примечание: - ниже показана иллюстрация функции map и flatmap, в противном случае Optional в первую очередь предназначен для использования только в качестве возвращаемого типа.

Как вы уже знаете, Optional - это вид контейнера, который может содержать или не содержать один объект, поэтому его можно использовать везде, где вы ожидаете нулевое значение (вы никогда не увидите NPE, если используете Optional должным образом). Например, если у вас есть метод, который ожидает объект person, который может быть обнуляемым, вы можете написать метод примерно так:

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
}

Здесь вы вернули тип String, который автоматически переносится в необязательный тип.

Если класс человека выглядел так, то есть телефон также необязательно

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

В этом случае вызывающая функция map обернет возвращаемое значение в Optional и выдаст что-то вроде:

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; Никогда не вызывайте метод get (если вам нужно) для Optional, не проверяя его с помощью isPresent (), если вы не можете жить без исключений NullPointerException.

SandeepGodara
источник
1
Я думаю, что этот пример может отвлечь от характера вашего ответа, потому что ваш класс Personзлоупотребляет Optional. Это противоречит намерению API использовать Optionalтаких членов - см. Mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/…
8bitjunkie
@ 8bitjunkie Спасибо за то, что указал на это, оно отличается от варианта Scala ..
SandeepGodara
6

Что мне помогло, так это взгляд на исходный код двух функций.

Карта - оборачивает результат в необязательный.

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 - возвращает необработанный объект

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
    }
}
Роберт Нестрой
источник
1
Что вы подразумеваете под flatMap«возвращает« необработанный »объект»? flatMapтакже возвращает сопоставленный объект, «обернутый» в Optional. Разница в том, что в случае flatMap, функция mapper оборачивает сопоставленный объект в то Optionalвремя как mapсам оборачивает объект Optional.
Дерек Махар
@DerekMahar удалил мой, нет необходимости повторно публиковать его, потому что вы правильно отредактировали свой комментарий.
maxxyme
3
  • Optional.map():

Принимает каждый элемент и, если значение существует, оно передается функции:

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

Добавленный теперь имеет одно из трех значений: trueили falseзаключенный в необязательный параметр , если optionalValueон присутствует, или пустой необязательный в противном случае.

Если вам не нужно обрабатывать результат, который вы можете просто использовать ifPresent(), у него нет возвращаемого значения:

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

Работает аналогично тому же методу потоков. Выравнивает поток потоков. С той разницей, что если значение представлено, оно применяется к функции. В противном случае возвращается пустой необязательный параметр.

Вы можете использовать его для составления необязательных значений функций вызовов.

Предположим, у нас есть методы:

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

Затем вы можете вычислить квадратный корень из обратного, например:

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

или, если вы предпочитаете:

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

Если какая- либо inverse()или squareRoot()возвращается Optional.empty(), то результат пуст.

nazar_art
источник
1
Это не компилируется. Оба ваших выражения возвращают необязательный <Double>, а не Double, которому вы присваиваете результат.
JL_SO
@JL_SO ты прав. Потому что обратный Optional<Double>тип имеет тип возвращаемого значения.
nazar_art
3

Ладно. Вам нужно использовать «flatMap» только тогда, когда вы сталкиваетесь с вложенными опциями . Вот пример.

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

}

Как и Stream, Optional # map вернет значение, заключенное в Optional. Вот почему мы получаем вложенный Optional - Optional<Optional<Insurance>. И в ②, мы хотим отобразить это как страховой случай, вот как произошла трагедия. Корень вложенный необязательно. Если мы сможем получить значение ядра независимо от оболочек, мы это сделаем. Это то, что делает flatMap.

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

В конце концов, я настоятельно рекомендую вам Java 8 In Action , если вы хотите систематически изучать Java8.

momonannan
источник