Java 8 - ความแตกต่างระหว่าง Optional.flatMap และ Optional.map


162

ความแตกต่างระหว่างสองวิธีนี้: Optional.flatMap()และOptional.map()?

ตัวอย่างจะได้รับการชื่นชม



5
@AlexisC ลิงก์ของคุณเกี่ยวกับแผนที่ของสตรีมและแผนที่ย่อไม่ใช่ที่เป็นทางเลือก
Eran

1
@Eran นั้นไม่สำคัญว่าถ้าคุณเข้าใจว่า map / flatMap ทำงานอย่างไรไม่ว่าจะเป็น Stream หรือไม่ก็เป็นตัวเลือกที่เหมือนกัน หากผู้ใช้เข้าใจวิธีการทำงานของสตรีมเขาไม่ควรถามคำถามนี้ แนวคิดนี้เหมือนกัน
Alexis C.

2
@AlexisC ไม่ได้จริงๆ flatMap ของตัวเลือกมีความเหมือนกันเล็กน้อยกับ flatMap ของสตรีม
Eran

1
@Eran ฉันพูดเกี่ยวกับแนวความคิดที่แตกต่างระหว่างแผนที่และ flatMap ที่ฉันไม่ได้ทำ correspondance แบบหนึ่งต่อหนึ่งระหว่างและStream#flatMap Optional#flatMap
Alexis C.

คำตอบ:


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/ ...... ) ดูเหมือนจะสำรองข้อมูลนี้ - " หากมีค่าให้ใช้ .. . "
Boris the Spider

1
@BoristheSpider เป็นตัวเลือกของ (null)! = ตัวเลือก.
ว่างเปล่า

14
@DiegoMartinoia เป็นOptional.of(null) . ExceptionOptional.ofNullable(null) == Optional.empty()
Boris the Spider

1
@BoristheSpider ใช่คุณพูดถูก ฉันพยายามที่จะตอบคำถามของคุณ แต่ฉันคิดว่าฉันทำให้มันชัดเจนยิ่งขึ้น: ในเชิงแนวคิดไม่จำเป็นต้องว่างเปล่า (null) ไม่ควรว่างเปล่า แต่ในทางปฏิบัติแล้วถือว่าเป็นแผนที่และดังนั้นแผนที่ / flatmap ไม่ทำงาน
Diego Martinoia

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>

มันเป็นองค์ประกอบของสองสำนวนการทำงาน: และmapflatten


7

หมายเหตุ: - ด้านล่างเป็นภาพประกอบของแผนที่และฟังก์ชั่น flatmap มิฉะนั้นเป็นตัวเลือกที่ได้รับการออกแบบมาเพื่อใช้เป็นประเภทผลตอบแทนเท่านั้น

ตามที่คุณทราบแล้วว่าตัวเลือกเป็นคอนเทนเนอร์ชนิดหนึ่งซึ่งอาจมีหรือไม่มีวัตถุเดียวดังนั้นจึงสามารถใช้งานได้ทุกที่ที่คุณคาดว่าจะมีค่า Null (คุณอาจไม่เคยเห็น 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
}

ที่นี่คุณได้คืนประเภทสตริงซึ่งถูกห่อในประเภทตัวเลือกโดยอัตโนมัติ

หากคลาสบุคคลมีลักษณะเช่นนี้โทรศัพท์ก็เป็นตัวเลือกเช่นกัน

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; อย่าเรียกวิธีรับ (ถ้าคุณต้องการ) บนตัวเลือกโดยไม่ต้องตรวจสอบด้วย 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 wraps วัตถุแมปในOptionalขณะที่mapตัวเอง wraps Optionalวัตถุใน
Derek Mahar

@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> ที่เป็นทางเลือกแทนที่จะเป็น 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);       // ②
    }

}

เช่นเดียวกับกระแสข้อมูล # แผนที่ตัวเลือกจะส่งคืนค่าที่ห่อหุ้มด้วยทางเลือก นั่นเป็นเหตุผลที่เราได้รับที่ซ้อนกันเป็นตัวเลือก Optional<Optional<Insurance>- และที่②เราต้องการแมปเป็นอินสแตนซ์ประกันภัยนั่นคือสิ่งที่เกิดขึ้นกับโศกนาฏกรรม รูตซ้อนซ้อนกันเป็นตัวเลือก หากเราสามารถรับค่าหลักโดยไม่คำนึงถึงเชลล์เราจะทำให้เสร็จ นั่นคือสิ่งที่ flatMap ทำ

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

ในท้ายที่สุดฉันขอแนะนำJava 8 In Actionแก่คุณหากคุณต้องการศึกษา Java8 อย่างเป็นระบบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.