ทางเลือก orElse ทางเลือกใน Java


138

ฉันได้ทำงานกับประเภทตัวเลือกใหม่ใน Java 8และฉันได้พบกับสิ่งที่ดูเหมือนเป็นการดำเนินการทั่วไปที่ไม่รองรับฟังก์ชัน: an "orElseOptional"

พิจารณารูปแบบต่อไปนี้:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

รูปแบบนี้มีหลายรูปแบบ แต่ต้องการให้ "orElse" เป็นทางเลือกที่ใช้ฟังก์ชันสร้างทางเลือกใหม่ซึ่งเรียกว่าเมื่อไม่มีรูปแบบปัจจุบันเท่านั้น

การใช้งานจะมีลักษณะดังนี้:

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

ฉันอยากรู้ว่ามีเหตุผลที่วิธีการดังกล่าวไม่มีอยู่จริงหรือไม่ถ้าฉันแค่ใช้ตัวเลือกเสริมโดยไม่ได้ตั้งใจและวิธีอื่น ๆ ที่ผู้คนคิดขึ้นมาเพื่อจัดการกับกรณีนี้

ฉันควรจะบอกว่าฉันคิดว่าโซลูชันที่เกี่ยวข้องกับคลาส / วิธีการยูทิลิตีที่กำหนดเองนั้นไม่สวยหรูเพราะคนที่ทำงานกับโค้ดของฉันไม่จำเป็นต้องรู้ว่ามีอยู่

นอกจากนี้หากใครทราบวิธีดังกล่าวจะรวมอยู่ใน JDK 9 หรือไม่และฉันจะเสนอวิธีการดังกล่าวได้ที่ไหน ดูเหมือนว่าจะเป็นการละเลย API สำหรับฉัน


13
ดูปัญหานี้ เพื่อชี้แจง: สิ่งนี้จะอยู่ใน Java 9 แล้ว - หากไม่อยู่ในการอัปเดต Java 8 ในอนาคต
Obicere

มันคือ! ขอบคุณไม่พบสิ่งนั้นในการค้นหาของฉัน
Yona Appletree

2
@Obicere ว่าปัญหาไม่ได้ใช้ที่นี่เพราะมันเป็นเรื่องเกี่ยวกับพฤติกรรมในที่ว่างเปล่าที่เลือกไม่ได้เกี่ยวกับผลทางเลือก ตัวเลือกมีอยู่แล้วorElseGet()สำหรับสิ่งที่ OP ต้องการเพียง แต่มันไม่ได้สร้างไวยากรณ์การเรียงซ้อนที่ดี
Marko Topolnik


1
บทแนะนำที่ยอดเยี่ยมเกี่ยวกับ Java Optional: codeflex.co/java-optional-no-more-nullpointerexception
John Detroit

คำตอบ:


88

นี่เป็นส่วนหนึ่งของ JDK 9 ในรูปแบบorซึ่งใช้เวลา a Supplier<Optional<T>>. ตัวอย่างของคุณจะเป็น:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

สำหรับรายละเอียดโปรดดูJavadocหรือโพสต์นี้ที่ฉันเขียน


ดี. นอกจากนี้ต้องมีอายุหนึ่งปีและฉันไม่ได้สังเกตเห็น เกี่ยวกับคำถามในบล็อกของคุณการเปลี่ยนประเภทการส่งคืนจะทำลายความเข้ากันได้ของไบนารีเนื่องจากคำแนะนำในการเรียกใช้ bytecode อ้างถึงลายเซ็นแบบเต็มรวมถึงประเภทการส่งคืนดังนั้นจึงไม่มีโอกาสเปลี่ยนประเภทการส่งคืนของifPresent. แต่อย่างไรก็ตามฉันคิดว่าชื่อifPresentไม่ดีอยู่แล้ว สำหรับวิธีการอื่น ๆ ทั้งหมดที่ไม่ได้แบก“อื่น” ในชื่อ (เช่นmap, filter, flatMap) มันก็ส่อให้เห็นว่าพวกเขาทำอะไรถ้าค่าไม่เป็นปัจจุบันดังนั้นจึงควรifPresent...
โฮล

ดังนั้นการเพิ่มOptional<T> perform(Consumer<T> c)วิธีการอนุญาตให้มีการผูกมัดperform(x).orElseDo(y)( orElseDoเป็นทางเลือกอื่นสำหรับข้อเสนอของคุณifEmptyเพื่อให้มีความสอดคล้องกันในelseชื่อของวิธีการทั้งหมดที่อาจทำบางอย่างสำหรับค่าที่ขาดหายไป) คุณสามารถเลียนแบบperformใน Java 9 ได้stream().peek(x).findFirst()แม้ว่าจะเป็นการใช้งาน API ในทางที่ผิดและยังไม่มีวิธีใดที่จะดำเนินการRunnableโดยไม่ต้องระบุConsumerในเวลาเดียวกันได้ ...
Holger

65

วิธี "ลองใช้บริการ" ที่สะอาดที่สุดสำหรับ API ปัจจุบันคือ:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

สิ่งสำคัญไม่ใช่ห่วงโซ่การดำเนินการ (คงที่) ที่คุณต้องเขียนเพียงครั้งเดียว แต่การเพิ่มบริการอื่นนั้นง่ายเพียงใด (หรือแก้ไขรายการบริการเป็นเรื่องทั่วไป) ที่นี่การเพิ่มหรือลบรายการเดียว()->serviceX(args)ก็เพียงพอแล้ว

Optionalเนื่องจากการประเมินผลขี้เกียจของลำธารไม่มีบริการจะถูกเรียกถ้าบริการก่อนหน้านี้กลับไม่ว่างเปล่า


13
แค่ใช้สิ่งนั้นในโปรเจ็กต์ขอบคุณพระเจ้าที่เราไม่ทำการตรวจสอบโค้ด
Ilya Smagin

3
มันสะอาดกว่าโซ่ "orElseGet" มาก แต่ก็อ่านยากกว่าเช่นกัน
slartidan

4
นี่เป็นสิ่งที่ถูกต้องอย่างแน่นอน ... แต่โดยสุจริตฉันใช้เวลาสักครู่ในการแยกวิเคราะห์และฉันก็ไม่มั่นใจว่ามันถูกต้อง โปรดทราบว่าตัวอย่างนี้เป็นเช่นนั้น แต่ฉันนึกภาพออกได้ว่ามีการเปลี่ยนแปลงเล็กน้อยที่จะทำให้ไม่มีการประเมินอย่างเฉื่อยชาหรือมีข้อผิดพลาดอื่น ๆ ที่จะแยกไม่ออกในทันที สำหรับฉันแล้วสิ่งนี้จัดอยู่ในหมวดหมู่ของฟังก์ชันที่จะดีพอ ๆ กับยูทิลิตี้
Yona Appletree

3
ฉันสงสัยว่าการสลับ.map(Optional::get)ด้วย.findFirst()จะทำให้ "อ่าน" ง่ายขึ้นหรือไม่เช่น.filter(Optional::isPresent).findFirst().map(Optional::get)สามารถ "อ่าน" เช่น "ค้นหาองค์ประกอบแรกในสตรีมที่ตัวเลือก :: isPresent เป็นจริงแล้วทำให้แบนโดยใช้ตัวเลือก :: get"?
schatten

3
ตลกดีฉันโพสต์วิธีแก้ปัญหาที่คล้ายกันมากสำหรับคำถามที่คล้ายกันเมื่อสองสามเดือนก่อน นี่เป็นครั้งแรกที่ฉันเจอสิ่งนี้
shmosel

34

มันไม่สวย แต่จะได้ผล:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup)เป็นรูปแบบที่มีประโยชน์พอสมควรสำหรับใช้กับOptionalไฟล์. หมายความว่า "ถ้าสิ่งนี้Optionalมีค่าvให้ฉันfunc(v)หรือให้ฉันsup.get()"

ในกรณีนี้เราโทรหาserviceA(args)และรับOptional<Result>ไฟล์. หากที่Optionalมีค่าvที่เราต้องการที่จะได้รับแต่ถ้ามันเป็นที่ว่างเปล่าที่เราต้องการที่จะได้รับOptional.of(v) serviceB(args)ล้าง - ทำซ้ำด้วยทางเลือกอื่น ๆ

การใช้รูปแบบอื่น ๆ คือ

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)

1
เมื่อฉันใช้กลยุทธ์นี้ eclipse จะพูดว่า: "method orElseGet (Supplier <? expands String>) ในประเภท Optional <String> ไม่สามารถใช้ได้กับอาร์กิวเมนต์ (() -> {})" ดูเหมือนจะไม่ เพื่อพิจารณาส่งคืนตัวเลือกกลยุทธ์ที่ถูกต้องบน String ??
chrismarx

@chrismarx () -> {}ไม่ส่งคืนOptionalไฟล์. คุณพยายามทำอะไรให้สำเร็จ?
มิชา

1
ฉันแค่พยายามทำตามตัวอย่าง การเชื่อมโยงการเรียกแผนที่ไม่ทำงาน บริการของฉันส่งคืนสตริงและแน่นอนว่าไม่มีตัวเลือก. map () ให้ใช้งานแล้ว
chrismarx

1
ทางเลือกที่อ่านได้อย่างยอดเยี่ยมสำหรับor(Supplier<Optional<T>>)Java 9 ที่กำลังจะมาถึง
David M.

1
@ Sheepy คุณเข้าใจผิด .map()ในที่ว่างเปล่าจะผลิตที่ว่างเปล่าOptional Optional
มิชา

27

บางทีนี่อาจเป็นสิ่งที่คุณต้องการ: รับคุณค่าจากทางเลือกหนึ่งหรืออีกทางหนึ่ง

Optional.orElseGetมิฉะนั้นคุณอาจต้องการที่จะมีลักษณะที่ นี่คือตัวอย่างของสิ่งที่ฉันคิดว่าคุณต้องการ:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));

2
นี่คือสิ่งที่อัจฉริยะมักทำ ประเมินตัวเลือกเป็นโมฆะและการห่อด้วยofNullableเป็นสิ่งที่ยอดเยี่ยมที่สุดที่ฉันเคยเห็นมา
Jin Kwon

5

สมมติว่าคุณยังใช้ JDK8 อยู่มีหลายตัวเลือก

ตัวเลือก # 1: สร้างวิธีการช่วยเหลือของคุณเอง

เช่น:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

เพื่อให้คุณสามารถทำได้:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

ตัวเลือก # 2: ใช้ไลบรารี

เช่น Google Guava's Optional รองรับการor()ทำงานที่เหมาะสม(เช่นเดียวกับ JDK9) เช่น:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(โดยที่แต่ละบริการส่งคืนcom.google.common.base.Optionalมากกว่าjava.util.Optional)


ฉันไม่พบOptional<T>.or(Supplier<Optional<T>>)ในเอกสาร Guava คุณมีลิงค์สำหรับสิ่งนั้นหรือไม่?
Tamas Hegedus

.orElseGet ส่งคืน T แต่ตัวเลือก :: ว่างคืนค่าตัวเลือก <T> Optional.ofNullable (........ orElse (null)) มีเอฟเฟกต์ที่ต้องการตามที่ @aioobe อธิบายไว้
Miguel Pereira

@TamasHegedus คุณหมายถึงในตัวเลือก # 1? เป็นการใช้งานที่กำหนดเองซึ่งอยู่ใน snipped ด้านบน ps: ขออภัยที่ตอบช้า
Sheepy

@MiguelPereira .orElseGet ในกรณีนี้จะส่งกลับตัวเลือก <T> เนื่องจากมันทำงานบนตัวเลือก <ตัวเลือก <T>>
Sheepy

2

สิ่งนี้ดูเหมือนจะเหมาะสำหรับการจับคู่รูปแบบและอินเทอร์เฟซตัวเลือกแบบดั้งเดิมมากขึ้นพร้อมการใช้งานบางอย่างและไม่มีเลย (เช่นในJavaslang , FunctionalJava ) หรือการใช้งานอาจจะขี้เกียจในcyclops -reactฉันเป็นผู้เขียนไลบรารีนี้

ด้วยcyclops-reactคุณยังสามารถใช้การจับคู่รูปแบบโครงสร้างกับประเภท JDK สำหรับตัวเลือกคุณสามารถจับคู่ในกรณีที่ในปัจจุบันและขาดผ่านรูปแบบของผู้เข้าชม มันจะเป็นแบบนี้ -

  import static com.aol.cyclops.Matchables.optional;

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