Java 8 - Perbedaan antara Optional.flatMap dan Optional.map

162

Apa perbedaan antara kedua metode ini: Optional.flatMap()dan Optional.map()?

Sebuah contoh akan dihargai.

independen
sumber
5
@AlexisC. Tautan Anda adalah tentang peta Stream dan flatMap, bukan Opsional.
Eran
1
@Ran Itu tidak masalah, jika Anda memahami cara kerja peta / flatMap apakah itu untuk Stream atau tidak, itu sama untuk Opsional. Jika op mengerti cara kerjanya untuk Stream, maka dia seharusnya tidak menanyakan pertanyaan ini. Konsepnya sama.
Alexis C.
2
@AlexisC. Tidak juga. FlatMap opsional memiliki sedikit kesamaan dengan flatMap Stream.
Eran
1
@Ran saya berbicara tentang perbedaan konseptual antara peta dan flatMap, saya tidak membuat korespondensi satu-ke-satu antara Stream#flatMapdan Optional#flatMap.
Alexis C.

Jawaban:

166

Gunakan mapjika fungsi mengembalikan objek yang Anda butuhkan atau flatMapjika fungsi mengembalikan sebuah Optional. Sebagai contoh:

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

Kedua pernyataan cetak mencetak hal yang sama.

assylias
sumber
5
Pertanyaan: apakah akan [flat]Mappernah memanggil fungsi pemetaan dengan input == null? Pemahaman saya adalah bahwa Optionalsortcuts jika tidak ada - [ JavaDoc ] ( docs.oracle.com/javase/8/docs/api/java/util/… ) tampaknya mendukung hal ini - " Jika suatu nilai hadir, terapkan .. . "
Boris the Spider
1
@BoristheSpider Optional.of (null)! = Optional.empty ()
Diego Martinoia
14
@DiegoMartinoia Optional.of(null)adalah seorang Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider
1
@ BoristheSpider ya, Anda benar ,. Saya mencoba untuk menjawab pertanyaan Anda, tetapi saya rasa saya membuatnya lebih tidak jelas: secara konseptual, Opsional. Tidak dapat dibatalkan (nol) TIDAK boleh kosong, tetapi dalam praktiknya dianggap demikian, dan oleh karena itu peta / flatmap tidak dieksekusi.
Diego Martinoia
1
Saya pikir input tidak boleh nol di getOutputOpt atau getOutput
DanyalBurke
55

Mereka berdua mengambil fungsi dari jenis opsional ke sesuatu.

map()menerapkan fungsi " apa adanya " pada opsional yang Anda miliki:

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

Apa yang terjadi jika fungsi Anda berasal dari fungsi T -> Optional<U>?
Hasil Anda sekarang adalah Optional<Optional<U>>!

Tentang apa flatMap()itu: jika fungsi Anda sudah mengembalikan Optional, flatMap()sedikit lebih pintar dan tidak membungkusnya dua kali, kembali Optional<U>.

Ini komposisi dari dua idiom fungsional: mapdan flatten.

Diego Martinoia
sumber
7

Catatan: - di bawah ini adalah ilustrasi fungsi peta dan flatmap, jika tidak, Opsional utamanya dirancang untuk digunakan sebagai tipe pengembalian saja.

Seperti yang sudah Anda ketahui, Opsional adalah sejenis wadah yang mungkin atau mungkin tidak mengandung satu objek, sehingga dapat digunakan di mana pun Anda mengantisipasi nilai nol (Anda mungkin tidak pernah melihat NPE jika menggunakan Opsional dengan benar). Sebagai contoh jika Anda memiliki metode yang mengharapkan objek orang yang dapat dibatalkan, Anda mungkin ingin menulis metode seperti ini:

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
}

Di sini Anda telah mengembalikan jenis String yang secara otomatis dibungkus dengan jenis Opsional.

Jika kelas orang terlihat seperti ini, yaitu ponsel juga Opsional

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

Dalam hal ini, memohon fungsi peta akan membungkus nilai yang dikembalikan dalam Opsional dan menghasilkan sesuatu seperti:

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; Jangan pernah panggil metode get (jika perlu) pada Opsional tanpa memeriksanya dengan isPresent () kecuali Anda tidak dapat hidup tanpa NullPointerExceptions.

SandeepGodara
sumber
1
Saya pikir contoh ini cenderung mengalihkan perhatian dari sifat jawaban Anda karena kelas Anda Personmenyalahgunakan Optional. Adalah bertentangan dengan niat API untuk menggunakan Optionalanggota seperti ini - lihat mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/…
8bitjunkie
@ 8bitjunkie Terima kasih telah menunjukkan hal itu, ini berbeda dari Opsi Scala ..
SandeepGodara
6

Apa yang membantu saya adalah melihat kode sumber dari kedua fungsi tersebut.

Peta - membungkus hasilnya dalam Opsional.

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 - mengembalikan objek 'mentah'

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
sumber
1
Apa yang Anda maksud dengan flatMap"mengembalikan objek 'mentah'"? flatMapjuga mengembalikan objek yang dipetakan "terbungkus" dalam Optional. Perbedaannya adalah bahwa dalam kasus flatMap, fungsi mapper membungkus objek yang dipetakan Optionalsementara mapitu sendiri membungkus objek Optional.
Derek Mahar
@DerekMahar menghapus milik saya, tidak perlu memposting ulang, karena Anda telah mengedit komentar Anda dengan benar.
maxxyme
3
  • Optional.map():

Mengambil setiap elemen dan jika nilainya ada, ia diteruskan ke fungsi:

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

Sekarang ditambahkan memiliki salah satu dari tiga nilai: trueatau falsedibungkus menjadi Opsional , jika optionalValueada, atau Opsional kosong sebaliknya.

Jika Anda tidak perlu memproses hasil yang hanya dapat Anda gunakan ifPresent(), itu tidak memiliki nilai kembali:

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

Bekerja serupa dengan metode stream yang sama. Ratakan aliran sungai. Dengan perbedaan bahwa jika nilai disajikan itu diterapkan pada fungsi. Kalau tidak, opsional kosong dikembalikan.

Anda dapat menggunakannya untuk membuat panggilan fungsi nilai opsional.

Misalkan kita memiliki metode:

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

Kemudian Anda dapat menghitung akar kuadrat dari invers, seperti:

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

atau, jika Anda lebih suka:

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

Jika salah satu inverse()atau squareRoot()mengembalikan Optional.empty(), hasilnya kosong.

nazar_art
sumber
1
Ini tidak dikompilasi. Kedua ekspresi Anda menghasilkan Opsional <Double> daripada Double yang Anda tetapkan hasilnya.
JL_SO
@ JL_SO Anda benar. Karena terbalik memiliki Optional<Double>tipe sebagai tipe pengembalian.
nazar_art
3

Baik. Anda hanya perlu menggunakan 'flatMap' ketika Anda menghadapi Opsional bersarang . Inilah contohnya.

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

}

Seperti Stream, peta # Opsional akan mengembalikan nilai yang dibungkus oleh Opsional. Itu sebabnya kami mendapatkan Opsional bersarang - Optional<Optional<Insurance>. Dan di ②, kami ingin memetakannya sebagai contoh Asuransi, begitulah tragedi itu terjadi. Akar bersarang Opsional. Jika kami bisa mendapatkan nilai inti terlepas dari cangkang, kami akan menyelesaikannya. Itulah yang dilakukan flatMap.

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

Pada akhirnya, saya merekomendasikan Java 8 In Action kepada Anda jika Anda ingin mempelajari Java8 secara sistematis.

momonannan
sumber