Java 8 - різниця між Optional.flatMap та Optional.map


162

Яка різниця між цими двома методами: Optional.flatMap()і Optional.map()?

Приклад був би вдячний.



5
@AlexisC. Ваше посилання стосується карти Stream та flatMap, не обов'язково.
Еран

1
@Eran Це не має значення, якщо ви розумієте, як працює карта / flatMap, незалежно від того, є вона для Stream чи ні, це те саме для необов'язкового. Якщо оператор зрозумів, як це працює для потоку, він не повинен задавати це питання. Поняття те саме.
Олексій К.

2
@AlexisC. Не зовсім. Необов’язковий flatMap має мало спільного з Stream's flatMap.
Еран

1
@Eran Я говорю про концептуальну різницю між картою та плоскою картою, я не роблю співвідношення «один на один» між 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);
}

Обидва заяви про друк друкують одне і те ж.


5
Питання: [flat]Mapколи-небудь називати функцію відображення за допомогою input == null? Я розумію, що Optionalсортування вирізняється, якщо воно відсутнє - здається, що [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ) це резервне копіювання - " Якщо значення присутнє, застосуйте .. . ".
Борис Павук

1
@BoristheSpider Optional.of (null)! = Необов’язково.empty ()
Дієго Мартінойя

14
@DiegoMartinoia Optional.of(null)- це Exception. Optional.ofNullable(null) == Optional.empty().
Борис Павук

1
@BoristheSpider так, ти маєш рацію. Я намагався відповісти на ваше запитання, але думаю, що я зробив це ще більш неясним: концептуально, Optional.ofNullable (null) НЕ повинен бути порожнім, але на практиці це вважається таким, і тому карта / 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() йдеться: якщо ваша функція вже повертає Optional, flatMap()трохи розумніша і не подвоює її двічі, повертаючись Optional<U>.

Це склад двох функціональних ідіом: mapі flatten.


7

Примітка: - нижче наведено ілюстрацію функції карти та плоскої карти, інакше необов'язково в першу чергу призначена для використання лише як тип повернення.

Як ви вже знаєте, необов'язково - це такий тип контейнера, який може містити один об'єкт або не може містити його, тому він може бути використаний у будь-якому місці, коли ви очікуєте нульове значення (ви ніколи не побачите NPE при правильному використанні необов'язкового). Наприклад, якщо у вас є метод, який очікує від людини об'єкта, який може бути зведеним нанівець, ви можете написати метод приблизно так:

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
}

У цьому випадку виклик функції карти оберне повернене значення в необов'язковий і отримає щось на кшталт:

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 (якщо вам потрібно) на необов'язковий, не перевіряючи його за допомогою isPresent (), якщо ви не можете жити без NullPointerExceptions.


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(), або результат повернення порожній.


1
Це не компілюється. Обидва ваші вирази повертають необов’язковий <Double>, а не подвійний, якому ви присвоюєте результат.
JL_SO

@JL_SO ви праві. Тому що інверс має Optional<Double>тип повернення.
nazar_art

3

Добре. Ви тільки потрібно використовувати «flatMap» , коли ви зіткнулися з вкладеними OPTIONALS . Ось приклад.

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<Optional<Insurance>. І в ② ми хочемо відобразити це як страховий екземпляр, ось так сталася трагедія. Корінь вкладений Необов’язково. Якщо ми зможемо отримати основне значення незалежно від оболонок, ми це зробимо. Ось що робить flatMap.

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

Зрештою, я рішуче рекомендував вам Java 8 в дії, якщо ви хочете систематично вивчати Java8.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.