ความแตกต่างระหว่าง `Optional.orElse ()` และ `Optional.orElseGet () '


206

ฉันพยายามเข้าใจความแตกต่างระหว่างOptional<T>.orElse()และOptional<T>.orElseGet()วิธีการ

คำอธิบายสำหรับorElse()วิธีการคือ"ส่งคืนค่าถ้ามีมิฉะนั้นส่งคืนค่าอื่น"

ในขณะที่คำอธิบายสำหรับorElseGet()วิธีการคือ"ส่งคืนค่าถ้ามีมิฉะนั้นให้เรียกใช้วิธีอื่นและส่งคืนผลลัพธ์ของการเรียกใช้นั้น"

orElseGet()วิธีการใช้อินเตอร์เฟซที่ใช้งานได้ผู้ผลิตซึ่งเป็นหลักไม่ใช้พารามิเตอร์ใด ๆ Tและผลตอบแทน

คุณจะต้องใช้ในสถานการณ์orElseGet()ใด หากคุณมีวิธีการที่T myDefault()ทำไมคุณจะไม่เพียงแค่ทำoptional.orElse(myDefault())มากกว่าoptional.orElseGet(() -> myDefault())?

ดูเหมือนจะไม่orElseGet()เป็นการเลื่อนการดำเนินการของแลมบ์ดาไปในภายหลังหรือบางสิ่งบางอย่างดังนั้นประเด็นของมันคืออะไร? (ฉันคิดว่ามันจะมีประโยชน์มากขึ้นถ้ามันคืนความปลอดภัยOptional<T>ที่get()ไม่เคยขว้าง a NoSuchElementExceptionและisPresent()กลับมาจริงเสมอ ... แต่เห็นได้ชัดว่าไม่ใช่มันแค่คืนTเหมือนorElse())

มีความแตกต่างอื่น ๆ ที่ฉันหายไปหรือไม่?


7
เหตุผลก็คือเมื่อคุณใช้orElseGetมันจะโทรหาซัพพลายเออร์เฉพาะในกรณีที่ไม่มีค่า
Alex Salauyou

9
อ่าเข้าใจแล้ว ดังนั้นในกรณีของวิธีการยังคงเรียกว่า แต่ค่าตอบแทนของมันเป็นเพียงไม่ได้ใช้ orElse()myDefault()
jbx

3
คำถามที่ยกมาเพราะจากสิ่งที่ฉันเคยเห็นความเข้าใจผิดหรือเพียงลืมที่จะใช้orElseGet()อาจส่งผลให้ข้อบกพร่องร้ายแรงบางอย่าง: medium.com/alphadev- Thoughts/…
softarn

คำอธิบายที่ดีอยู่ที่นี่: baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

คำตอบ:


172

ใช้สถานการณ์ทั้งสองนี้:

Optional<Foo> opt = ...
Foo x = opt.orElse( new Foo() );
Foo y = opt.orElseGet( Foo::new );

หากoptไม่มีค่าทั้งสองจะเทียบเท่ากันแน่นอน แต่ถ้าopt ไม่มีค่ากี่Fooวัตถุจะถูกสร้างขึ้น?

Ps: แน่นอนในตัวอย่างนี้ความแตกต่างอาจไม่สามารถวัดได้ แต่ถ้าคุณต้องรับค่าเริ่มต้นจากบริการเว็บระยะไกลหรือจากฐานข้อมูลมันก็กลายเป็นสิ่งที่สำคัญมาก


22
ขอบคุณสำหรับคำชี้แจง ดังนั้นความแตกต่างนั้นลึกซึ้ง แต่สำคัญ ในกรณีที่สองก็จะไม่สร้างใหม่Fooวัตถุในขณะที่ในกรณีแรกจะสร้างมันขึ้นมา Optionalแต่ไม่ได้ใช้มันหากมีค่าภายใน
jbx

5
@jbx ใช่และในตัวอย่างที่น่าเบื่อของฉันมันไม่ได้สร้างความแตกต่างอย่างแท้จริง แต่ถ้าคุณต้องรับค่าเริ่มต้นจากบริการเว็บระยะไกลเช่นจากฐานข้อมูลความแตกต่างก็สำคัญมาก
biziclop

2
@jbx: คุณกำลังผสมสองสิ่งเข้าด้วยกัน มีคำถามเกี่ยวกับ SO เกี่ยวกับผลลัพธ์การเปรียบเทียบที่แปลกประหลาดซึ่งเกิดจากการไม่ใช้ผลลัพธ์ของการคำนวณ JVM สามารถทำได้ บนมืออื่น ๆ ที่System.out.println()เป็นไม่ได้คำนวณ แต่คำสั่งผลิตที่สังเกตผลข้างเคียง และฉันก็บอกว่าผลข้างเคียงที่สังเกตได้จะขัดขวางการปรับให้เหมาะสม (คอนโซลเอาต์พุตเป็นทรัพยากรภายนอก)
Holger

7
นั่นเป็นครั้งแรกที่ฉันเห็นคำถามแทนที่จะยอมรับคำตอบ
คิริลล์จี

4
" หากคุณต้องรับค่าเริ่มต้นจากบริการเว็บระยะไกลเช่น " นี่เป็นสถานการณ์ของฉันอย่างแน่นอน ในกรณีของฉันตัวเลือกคือแบบสอบถามและค่าเริ่มต้นหากไม่มีคิวรีคือการดึงค่าทั้งหมด ... ใช่หรือElseGetลดจำนวนรันไทม์ของการดำเนินการนั้นลง 1,000 ครั้ง
scottysseus

109

คำตอบสั้น ๆ :

  • orElse ()จะเรียกใช้ฟังก์ชันที่กำหนดเสมอไม่ว่าคุณต้องการหรือไม่ก็ตามโดยไม่คำนึงถึงOptional.isPresent()คุณค่า
  • orElseGet ()จะเรียกใช้ฟังก์ชันที่กำหนดเมื่อOptional.isPresent() == false

ในรหัสจริงคุณอาจต้องการที่จะต้องพิจารณาวิธีการที่สองเมื่อทรัพยากรที่จำเป็นคือราคาแพงที่จะได้รับ

// Always get heavy resource
getResource(resourceId).orElse(getHeavyResource()); 

// Get heavy resource when required.
getResource(resourceId).orElseGet(() -> getHeavyResource()) 

สำหรับรายละเอียดเพิ่มเติมให้พิจารณาตัวอย่างต่อไปนี้ด้วยฟังก์ชั่นนี้:

public Optional<String> findMyPhone(int phoneId)

ความแตกต่างดังต่อไปนี้:

                           X : buyNewExpensivePhone() called

+——————————————————————————————————————————————————————————————————+——————————————+
|           Optional.isPresent()                                   | true | false |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElse(buyNewExpensivePhone())          |   X  |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElseGet(() -> buyNewExpensivePhone()) |      |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+

เมื่อoptional.isPresent() == falseไม่มีความแตกต่างระหว่างสองวิธี อย่างไรก็ตามเมื่อใดoptional.isPresent() == trueก็ตามที่orElse()เรียกใช้ฟังก์ชันที่ตามมาไม่ว่าคุณจะต้องการหรือไม่ก็ตาม

ในที่สุดกรณีทดสอบที่ใช้มีดังนี้:

ผลลัพธ์:

------------- Scenario 1 - orElse() --------------------
  1.1. Optional.isPresent() == true
    Going to a very far store to buy a new expensive phone
    Used phone: MyCheapPhone

  1.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

------------- Scenario 2 - orElseGet() --------------------
  2.1. Optional.isPresent() == true
    Used phone: MyCheapPhone

  2.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

รหัส:

public class TestOptional {
    public Optional<String> findMyPhone(int phoneId) {
        return phoneId == 10
                ? Optional.of("MyCheapPhone")
                : Optional.empty();
    }

    public String buyNewExpensivePhone() {
        System.out.println("\tGoing to a very far store to buy a new expensive phone");
        return "NewExpensivePhone";
    }


    public static void main(String[] args) {
        TestOptional test = new TestOptional();
        String phone;
        System.out.println("------------- Scenario 1 - orElse() --------------------");
        System.out.println("  1.1. Optional.isPresent() == true");
        phone = test.findMyPhone(10).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  1.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("------------- Scenario 2 - orElseGet() --------------------");
        System.out.println("  2.1. Optional.isPresent() == true");
        // Can be written as test::buyNewExpensivePhone
        phone = test.findMyPhone(10).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  2.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");
    }
}

ฉันคิดว่าคุณอาจมีข้อผิดพลาดในภาพของคุณมันควรจะพูดว่า "orElseGet" ทางด้านขวาหรือไม่ นอกจากนั้นตัวอย่างที่ดี
Yalla T.

ใช่คุณถูกต้อง. ขอบคุณ :) ฉันจะอัปเดตในอีกไม่กี่ชั่วโมงข้างหน้า
nxhoaf

สำหรับสัญลักษณ์แสดงหัวข้อย่อยที่สองดูเหมือนว่าควรจะOptional.isPresent() == falseแทน (เท็จไม่เป็นความจริง)
มานูเอลจอร์แดน

ตัวอย่างที่ดี - แต่จริง ๆ แล้วฉันไม่เข้าใจว่า Javadocs Optional.orElseซึ่งรัฐIf a value is present, returns the value, otherwise returns otherสามารถบ่งบอกถึงพฤติกรรมนี้ได้อย่างไร ...
Erik Finnman

จากคำอธิบายของคุณสำหรับฉันมันดูเหมือนว่าorElse()จะมีพฤติกรรมคล้ายกับfinallyในtry-catchการแสดงออก ฉันถูกไหม?
Mike B.

63

ฉันถึงที่นี่สำหรับปัญหาKudoกล่าวถึง

ฉันแบ่งปันประสบการณ์ของฉันให้ผู้อื่น

orElseหรือorElseGetนั่นคือคำถาม:

static String B() {
    System.out.println("B()...");
    return "B";
}

public static void main(final String... args) {
    System.out.println(Optional.of("A").orElse(B()));
    System.out.println(Optional.of("A").orElseGet(() -> B()));
}

พิมพ์

B()...
A
A

orElseประเมินค่าของ B () ขึ้นอยู่กับค่าของทางเลือก ดังนั้นorElseGetขี้เกียจ


7
มันไม่ใช่ปัญหา'. มันเป็นความจริงง่ายๆที่อาร์กิวเมนต์สำหรับวิธีการประเมินก่อนที่จะดำเนินการวิธีการ หากคุณผ่านB()ไปยังวิธีการตั้งชื่อorElse()หรือabc()ไม่ได้สร้างความแตกต่างใด ๆB()ได้รับการประเมิน
jbx

11
ปัญหานี่คือการตั้งชื่อของวิธีการจริงๆ orพัฒนาคำนำหน้าเข้าใจผิด (รวมทั้งตัวเองเมื่อฉันถามปัญหา) ในการคิดว่ามันคือการดำเนินการลัดวงจร-เพราะนั่นคือสิ่งที่เราจะใช้ในการอยู่ในสภาพแบบบูล อย่างไรก็ตามมันไม่ใช่มันเป็นเพียงชื่อเมธอดที่มีorอยู่ในคำนำหน้าดังนั้นข้อโต้แย้งของมันจะถูกประเมินโดยไม่คำนึงว่าOptionalจะมีค่าหรือไม่ เป็นเรื่องน่าเสียดายที่การตั้งชื่อนั้นสร้างความสับสนไม่ใช่ว่าเราสามารถทำอะไรกับมันได้
jbx

37

ฉันจะบอกความแตกต่างที่ใหญ่ที่สุดระหว่างorElseและorElseGetมาเมื่อเราต้องการประเมินบางสิ่งเพื่อรับค่าใหม่ในelseสภาพ

พิจารณาตัวอย่างง่ายๆนี้ -

// oldValue is String type field that can be NULL
String value;
if (oldValue != null) {
    value = oldValue;
} else {
    value = apicall().value;
}

ตอนนี้ขอเปลี่ยนตัวอย่างข้างต้นจะใช้Optionalพร้อมกับorElse,

// oldValue is Optional type field
String value = oldValue.orElse(apicall().value);

ตอนนี้ขอเปลี่ยนตัวอย่างข้างต้นจะใช้Optionalพร้อมกับorElseGet,

// oldValue is Optional type field
String value = oldValue.orElseGet(() -> apicall().value);

เมื่อไหร่ orElseถูกเรียกการapicall().valueประเมินและส่งผ่านไปยังวิธีการ ในกรณีของorElseGetการประเมินจะเกิดขึ้นเฉพาะในกรณีที่oldValueว่างเปล่า orElseGetช่วยให้การประเมินผลขี้เกียจ


4
ฉันเสียเวลามากเพราะพฤติกรรม "แปลก" ของ ifElse () นี้ ฉันว่ามันเหมาะสมกว่า ifElseGet () มากกว่า ifElse ()
Enrico Giurin

3

ตัวอย่างต่อไปนี้ควรแสดงให้เห็นถึงความแตกต่าง:

String destroyTheWorld() {
  // destroy the world logic
  return "successfully destroyed the world";
}

Optional<String> opt = Optional.empty();

// we're dead
opt.orElse(destroyTheWorld());

// we're safe    
opt.orElseGet(() -> destroyTheWorld());

คำตอบจะปรากฏในเอกสารเช่นกัน

public T orElseGet(Supplier<? extends T> other):

ส่งคืนค่าถ้ามีมิฉะนั้นเรียกใช้อื่น ๆ และส่งคืนผลลัพธ์ของการเรียกใช้นั้น

Supplier จะไม่ถูกเรียกถ้าOptionalของขวัญ ในขณะที่

public T orElse(T other):

ส่งคืนค่าถ้ามีมิฉะนั้นส่งคืนค่าอื่น

หากotherเป็นวิธีที่ส่งกลับสตริงสตริงนั้นจะถูกเรียกใช้ แต่จะไม่ส่งคืนค่าในกรณีที่Optionalมีอยู่


3

ความแตกต่างนั้นค่อนข้างบอบบางและถ้าคุณไม่ใส่ใจมากคุณจะใช้มันในทางที่ผิด

วิธีที่ดีที่สุดที่จะเข้าใจความแตกต่างระหว่างorElse()และorElseGet()คือorElse()จะถูกดำเนินการเสมอถ้าOptional<T>เป็นโมฆะหรือไม่แต่orElseGet()จะถูกดำเนินการเมื่อOptional<T>เป็นโมฆะเท่านั้นโมฆะ

ความหมายของพจนานุกรมของorElseคือ: -ดำเนินการส่วนที่ไม่มีสิ่งใดปรากฏ แต่ที่นี่ขัดแย้งกันโปรดดูตัวอย่างด้านล่าง:

    Optional<String> nonEmptyOptional = Optional.of("Vishwa Ratna");
    String value = nonEmptyOptional.orElse(iAmStillExecuted());

    public static String iAmStillExecuted(){
    System.out.println("nonEmptyOptional is not NULL,still I am being executed");
    return "I got executed";
    }

เอาต์พุต: nonEmptyOptional ไม่ใช่ NULL แต่ฉันยังคงถูกเรียกใช้งาน


    Optional<String> emptyOptional = Optional.ofNullable(null);
    String value = emptyOptional.orElse(iAmStillExecuted());
    public static String iAmStillExecuted(){
    System.out.println("emptyOptional is NULL, I am being executed, it is normal as 
    per dictionary");
    return "I got executed";
    }

เอาต์พุต : emptyOptional เป็น NULL ฉันกำลังถูกเรียกใช้มันเป็นเรื่องปกติตามพจนานุกรม

สำหรับorElseGet()วิธีการไปตามความหมายในพจนานุกรมที่ orElseGet()ส่วนหนึ่งจะถูกดำเนินการเฉพาะเมื่อตัวเลือกเป็น โมฆะ

มาตรฐาน :

+--------------------+------+-----+------------+-------------+-------+
| Benchmark          | Mode | Cnt | Score      | Error       | Units |
+--------------------+------+-----+------------+-------------+-------+
| orElseBenchmark    | avgt | 20  | 60934.425  | ± 15115.599 | ns/op |
+--------------------+------+-----+------------+-------------+-------+
| orElseGetBenchmark | avgt | 20  | 3.798      | ± 0.030     | ns/op |
+--------------------+------+-----+------------+-------------+-------+

ข้อสังเกต : orElseGet()มีประสิทธิภาพสูงกว่าอย่างชัดเจนorElse()สำหรับตัวอย่างเฉพาะของเรา

หวังว่ามันจะเคลียร์ข้อสงสัยของคนอย่างฉันที่ต้องการตัวอย่างพื้นฐานที่ดีมาก :)


2

ก่อนอื่นให้ตรวจสอบการประกาศของทั้งสองวิธี

1) OrElse: เรียกใช้งานตรรกะและส่งผ่านผลลัพธ์เป็นอาร์กิวเมนต์

public T orElse(T other) {    
 return value != null ? value : other;
}

2) OrElseGet: เรียกใช้งานลอจิกหากค่าภายในตัวเลือกเป็นโมฆะ

public T orElseGet(Supplier<? extends T> other) {
  return value != null ? value : other.get(); 
}

คำอธิบายบางประการเกี่ยวกับการประกาศข้างต้น: อาร์กิวเมนต์ของ“ Optional.orElse” มักจะถูกดำเนินการโดยไม่คำนึงถึงมูลค่าของวัตถุในตัวเลือก (null ว่างเปล่าหรือมีค่า) พิจารณาประเด็นที่กล่าวถึงข้างต้นเสมอในขณะที่ใช้“ ไม่ระบุหรือ orElse” มิฉะนั้นการใช้“ ไม่บังคับหรือไม่ก็ได้” อาจมีความเสี่ยงสูงในสถานการณ์ต่อไปนี้

ความเสี่ยง -1) ปัญหาการบันทึก:หากเนื้อหาภายในหรือปิดมีคำสั่งบันทึกใด ๆ : ในกรณีนี้คุณจะต้องทำการบันทึกทุกครั้ง

Optional.of(getModel())
   .map(x -> {
      //some logic
   })
  .orElse(getDefaultAndLogError());

getDefaultAndLogError() {
  log.error("No Data found, Returning default");
  return defaultValue;
}

ความเสี่ยง -2) ปัญหาด้านประสิทธิภาพ:หากเนื้อหาภายในหรือหมดเวลา: เนื้อหาที่ใช้เวลามากอาจเป็นการเรียกใช้ฐานข้อมูล i / o ใด ๆ , การเรียก API, การเรียก API, การอ่านไฟล์ หากเราใส่เนื้อหาดังกล่าวใน orElse () ระบบจะจบการทำงานโดยไม่ต้องใช้รหัส

Optional.of(getModel())
   .map(x -> //some logic)
   .orElse(getDefaultFromDb());

getDefaultFromDb() {
   return dataBaseServe.getDefaultValue(); //api call, db call.
}

ความเสี่ยง -3) สถานะที่ผิดกฎหมายหรือปัญหาบั๊ก:หากเนื้อหาภายใน orElse เปลี่ยนแปลงสถานะของวัตถุบางอย่าง: เราอาจใช้วัตถุเดียวกันในที่อื่นให้พูดในฟังก์ชัน Optional.map และอาจทำให้เรามีข้อผิดพลาดร้ายแรง

List<Model> list = new ArrayList<>();
Optional.of(getModel())
  .map(x -> {
  })
  .orElse(get(list));

get(List < String > list) {
   log.error("No Data found, Returning default");
   list.add(defaultValue);
   return defaultValue;
}

จากนั้นเราจะไปกับ orElse () ได้เมื่อใด ต้องการใช้ orElse เมื่อค่าเริ่มต้นคือวัตถุคงที่บางอย่าง enum ในกรณีข้างต้นทั้งหมดเราสามารถใช้ตัวเลือก Option.orElseGet () ซึ่งจะทำงานเมื่อตัวเลือกนั้นมีค่าที่ไม่ว่างเปล่า) แทนตัวเลือก Option.orElse () ทำไม?? ใน orElse เราส่งค่าผลลัพธ์เริ่มต้น แต่ใน orElseGet เราส่งผู้จำหน่ายและวิธีการของผู้ผลิตจะดำเนินการเฉพาะในกรณีที่ค่าในตัวเลือกเป็นโมฆะ

ประเด็นสำคัญจากสิ่งนี้:

  1. อย่าใช้“ ทางเลือก. orselse” หากมีคำสั่งบันทึกใด ๆ
  2. อย่าใช้“ เผื่อเลือกหรือไม่” หากมีตรรกะที่ใช้เวลามาก
  3. อย่าใช้“ ทางเลือกหรือไม่ก็ได้” หากมันทำการเปลี่ยนแปลงสถานะของวัตถุ
  4. ใช้“ ทางเลือก. หรือ” หากเราต้องส่งคืนค่าคงที่, enum
  5. ชอบ“ Optional.orElseGet” ในสถานการณ์ที่กล่าวถึงใน 1,2 และ 3 คะแนน

ฉันได้อธิบายสิ่งนี้ในจุดที่ 2 ( “ Optional.map/Optional.orElse”! =“ if / else” ) บล็อกขนาดกลางของฉัน ใช้ Java8 เป็นโปรแกรมเมอร์ไม่ใช่ coder


0

พิจารณารหัสต่อไปนี้:

import java.util.Optional;

// one class needs to have a main() method
public class Test
{
  public String orelesMethod() {
    System.out.println("in the Method");
    return "hello";
  }

  public void test() {
    String value;
    value = Optional.<String>ofNullable("test").orElseGet(this::orelesMethod);
    System.out.println(value); 

    value = Optional.<String>ofNullable("test").orElse(orelesMethod());
    System.out.println(value); 
  }

  // arguments are passed using the text field below this editor
  public static void main(String[] args)
  {
    Test test = new Test();

    test.test();
  }
}

ถ้าเราได้รับvalueในวิธีนี้Optional.<String>ofNullable(null)มีความแตกต่างระหว่าง orElseGet () และ orElse () ไม่ได้ แต่ถ้าเราได้รับvalueในลักษณะนี้: Optional.<String>ofNullable("test"), orelesMethod()ในorElseGet()จะไม่ถูกเรียก แต่orElse()มันจะถูกเรียกว่า

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