ใช้เป็นทางเลือก


271

เมื่อใช้ Java 8 มาเป็นเวลา 6 เดือนหรือมากกว่านั้นฉันมีความสุขมากกับการเปลี่ยนแปลง API ใหม่ Optionalหนึ่งในพื้นที่ที่ฉันยังคงไม่มั่นใจในความเป็นเมื่อมีการใช้งาน ดูเหมือนว่าฉันจะแกว่งไปมาระหว่างที่ต้องการใช้มันทุกที่ที่เป็นไปได้nullและไม่มีที่ไหนเลย

ดูเหมือนจะมีหลายสถานการณ์ที่ฉันสามารถใช้งานได้และฉันไม่แน่ใจว่ามันจะเพิ่มประโยชน์ (ความปลอดภัยในการอ่าน / ความปลอดภัยเป็นโมฆะ) หรือเพียงแค่ทำให้เกิดค่าใช้จ่ายเพิ่มเติม

ดังนั้นฉันมีตัวอย่างเล็ก ๆ น้อย ๆ และฉันสนใจในความคิดของชุมชนว่าOptionalมีประโยชน์หรือไม่

1 - ในฐานะที่เป็นวิธีการที่สาธารณะประเภทกลับเมื่อวิธีการที่จะกลับมาnull:

public Optional<Foo> findFoo(String id);

2 - เป็นพารามิเตอร์เมธอดเมื่อพารามิเตอร์อาจเป็นnull:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 - ในฐานะสมาชิกเสริมของ bean:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4 - ในCollections:

โดยทั่วไปฉันไม่คิดว่า:

List<Optional<Foo>>

เพิ่มอะไร - โดยเฉพาะอย่างยิ่งเนื่องจากสามารถใช้filter()เพื่อลบnullค่า ฯลฯ แต่มีการใช้ที่ดีสำหรับOptionalในคอลเลกชัน?

กรณีใดที่ฉันพลาด?


2
กรณีหนึ่งที่ฉันพบว่ามีประโยชน์คือตัวอย่างเช่นถ้าคุณมีแผนที่ทดแทน Map<Character, String>เช่น Optional.ofNullable(map.get(c)).orElse(String.valueOf(c))หากไม่มีการเปลี่ยนตัวฉันสามารถใช้นี้ นอกจากนี้ทราบว่าเป็นตัวเลือกที่ถูกขโมยมาจากฝรั่งและมันมีไวยากรณ์มาก nicer:Optional.fromNullable(map.get(c)).or(String.valueOf(c));
FGE

3
นอกจากนี้ในคอลเลกชันก็มีคอลเล็กชันที่ไม่อนุญาตให้มีค่า Null! ตัวเลือกที่เหมาะกับการเรียกเก็บเงินที่นี่ และคุณสามารถ.filter(Optional::absent)"ค่า Null" ได้
fge

2
@fge ในความเป็นธรรมทั้งหมดฉันคิดว่าแนวคิดของตัวเลือกแท้จริงมาจาก FP
VH-NZZ

2
@fge ไม่ได้แสดงที่ดีขึ้นด้วยgetOrDefault()?
Jim Garrison

คำตอบ:


206

จุดหลักของOptionalคือการให้วิธีการสำหรับฟังก์ชั่นการส่งกลับค่าเพื่อระบุการขาดค่าตอบแทน ดูการสนทนานี้ สิ่งนี้จะช่วยให้ผู้โทรดำเนินการต่อไปของการเรียกใช้เมธอดอย่างคล่องแคล่ว

ตรงนี้มากที่สุดใช้ case # 1ในคำถามของ OP แม้ว่าการไม่มีค่าจะเป็นสูตรที่แม่นยำกว่าค่า nullเนื่องจากบางอย่างเช่นIntStream.findFirstไม่สามารถคืนค่าว่างได้

สำหรับกรณีการใช้งาน# 2 การส่งผ่านอาร์กิวเมนต์ที่เป็นตัวเลือกไปยังวิธีการนี้อาจใช้งานได้ แต่มันค่อนข้างงุ่มง่าม สมมติว่าคุณมีเมธอดที่รับสตริงตามด้วยสตริงที่สองเผื่อเลือก การยอมรับข้อOptionalโต้แย้งที่สองจะส่งผลให้เกิดรหัสดังนี้:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

การยอมรับ null เป็น nicer:

foo("bar", "baz");
foo("bar", null);

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

foo("bar", "baz");
foo("bar");

สิ่งนี้มีข้อ จำกัด แต่ก็ดีกว่าข้อใดข้อหนึ่งข้างต้น

ใช้กรณี# 3และ# 4 ที่มีOptionalในฟิลด์คลาสหรือในโครงสร้างข้อมูลถือเป็นการใช้ API ในทางที่ผิด ขั้นแรกมันขัดกับเป้าหมายการออกแบบหลัก ๆOptionalตามที่ระบุไว้ด้านบน ประการที่สองจะไม่เพิ่มมูลค่าใด ๆ

มีสามวิธีในการจัดการกับการไม่มีค่าในOptional: เพื่อจัดเตรียมค่าทดแทนเพื่อเรียกใช้ฟังก์ชันเพื่อจัดเตรียมค่าทดแทนหรือเพื่อโยนข้อยกเว้น หากคุณกำลังจัดเก็บลงในเขตข้อมูลคุณจะทำเช่นนี้ในการเริ่มต้นหรือเวลาที่ได้รับมอบหมาย หากคุณกำลังเพิ่มค่าลงในรายการตามที่ OP ได้กล่าวไว้คุณมีตัวเลือกเพิ่มเติมเพียงแค่ไม่เพิ่มค่าดังนั้น "แบน" ค่าที่ขาด

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


46
ฉันไม่เห็นด้วยกับ # 3 มันจะมีประโยชน์ที่จะมีOptionalเป็นสนามเมื่อสิ่งที่จะต้องทำหรือไม่ต้องทำเมื่อมีหรือไม่มีค่า
glglgl

15
ผมไม่เห็นว่าทำไมทำภายในจะดีกว่าเพียงแค่การจัดเก็บข้อมูลเป็นif(foo == null) optionalและผมไม่เห็นเหตุผลที่ชัดเจนโทรภายในจะดีกว่าเพียงแค่การจัดเก็บข้อมูลในฐานะที่เป็นgetOptionalFoo() Optionalนอกจากนี้ถ้าเขตข้อมูลหนึ่งสามารถnullและเขตข้อมูลหนึ่งไม่สามารถnullทำไมไม่สื่อสารที่เรียบเรียงโดยทำให้พวกเขาหรือไม่Optional Optional
mogronalol

27
@ StuartMarks นี่เป็นหนึ่งในสิ่งที่ทำให้ฉันงุนงงเมื่อได้ยินจากOptional<>ผู้เชี่ยวชาญทั้งหมด: "อย่าเก็บไว้Optional<>ในทุ่ง" ฉันพยายามเข้าใจจุดแนะนำนี้อย่างแท้จริง พิจารณาทรี DOM ที่โหนดสามารถมีพาเรนต์หรือไม่ ดูเหมือนว่าภายในกรณีการใช้งานที่จะกลับมาNode.getParent() Optional<Node>ฉันคาดว่าจะจัดเก็บnullและปิดผลในแต่ละครั้งหรือไม่? ทำไม? ความไร้ประสิทธิภาพพิเศษนี้ซื้ออะไรให้ฉันนอกจากทำให้โค้ดดูน่าเกลียด?
Garret Wilson

7
@GarretWilson บนมืออื่น ๆ Optional<Node> getParent() { return Optional.ofNullable(parent); }สมมติว่าคุณมี มันดูแพงเพราะจัดสรรOptionalทุกครั้ง! แต่ถ้าผู้โทรปลดมันทันทีวัตถุนั้นมีอายุสั้นมากและไม่ได้รับการเลื่อนตำแหน่งออกจากพื้นที่ eden อาจเป็นไปได้ว่าจะถูกกำจัดออกโดยการวิเคราะห์การหลบหนีของ JIT คำตอบที่บรรทัดล่างคือ "มันขึ้นอยู่กับ" เช่นเคย แต่การใช้Optionalในเขตข้อมูลที่อาจใช้หน่วยความจำ bloats และช้าลงการสำรวจโครงสร้างข้อมูล และในที่สุดฉันก็คิดว่ามันเป็นรหัสขึ้นมา แต่นี่เป็นเรื่องของรสนิยม
Stuart Marks

6
@GarretWilson คุณเขียนบล็อกเพื่อสิ่งนั้นหรือไม่? ฉันจะมีความสนใจใน :) ว่า
akhil_mittal

78

ฉันไปเล่นเกมช้า แต่สิ่งที่คุ้มค่าฉันต้องการเพิ่ม 2 Cents ของฉัน พวกเขาขัดกับเป้าหมายการออกแบบOptionalซึ่งสรุปได้ดีจากคำตอบของ Stuart Marksแต่ฉันก็ยังเชื่อในความถูกต้องของมัน (ชัด)

ใช้ตัวเลือกทุกที่

โดยทั่วไป

ฉันเขียนโพสต์บล็อกOptionalทั้งหมดเกี่ยวกับการใช้แต่โดยพื้นฐานแล้วมาที่นี่:

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

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

โปรดทราบว่าการดำเนินการนี้จะไม่อนุญาตให้ใช้Optionalกับคอลเล็กชันซึ่งเกือบจะไม่ดีเท่าnulls อย่าทำอย่างนั้น ;)

เกี่ยวกับคำถามของคุณ

  1. ใช่.
  2. หากการใช้งานมากเกินไปไม่มีตัวเลือกใช่
  3. หากวิธีการอื่น (subclassing, decorating, ... ) ไม่มีตัวเลือกใช่
  4. ได้โปรดอย่า!

ข้อดี

การทำเช่นนี้จะช่วยลดการปรากฏตัวของnulls ในฐานรหัสของคุณแม้ว่าจะไม่ได้กำจัดพวกเขา แต่นั่นไม่ใช่ประเด็นหลัก มีข้อดีที่สำคัญอื่น ๆ :

ชี้แจงเจตนา

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

ลบความไม่แน่นอนออก

หากไม่มีOptionalความหมายของnullเหตุการณ์ก็ไม่ชัดเจน มันอาจจะเป็นตัวแทนทางกฎหมายของรัฐ (ดูMap.get) หรือข้อผิดพลาดในการใช้งานเช่นการเริ่มต้นที่ขาดหายไปหรือล้มเหลว

Optionalการเปลี่ยนแปลงนี้อย่างมากกับการใช้งานถาวรของ ที่นี่แล้วการเกิดขึ้นของnullบ่งบอกถึงการปรากฏตัวของข้อบกพร่อง (เพราะหากค่าได้รับอนุญาตให้หายไปOptionalจะมีการใช้งาน) สิ่งนี้ทำให้การดีบักข้อยกเว้นตัวชี้โมฆะง่ายขึ้นมากเนื่องจากคำถามของความหมายของnullคำตอบนี้ได้ถูกตอบแล้ว

ตรวจสอบ Null เพิ่มเติม

ตอนนี้ไม่มีอะไรที่จะสามารถทำได้nullอีกต่อไป ไม่ว่าจะมีคำอธิบายประกอบการยืนยันหรือการตรวจสอบธรรมดาคุณไม่ต้องคิดว่าอาร์กิวเมนต์นี้หรือประเภทการส่งคืนนั้นอาจเป็นโมฆะ มันเป็นไปไม่ได้!

ข้อเสีย

แน่นอนว่าไม่มีกระสุนเงิน ...

ประสิทธิภาพ

การห่อค่า (โดยเฉพาะอย่างยิ่งการใช้พื้นฐาน) ลงในอินสแตนซ์พิเศษสามารถทำให้ประสิทธิภาพลดลง ในลูปแน่นนี้อาจกลายเป็นที่สังเกตได้หรือแย่ลง

โปรดทราบว่าคอมไพเลอร์อาจหลีกเลี่ยงการอ้างอิงพิเศษสำหรับอายุการใช้งานสั้น ๆ ของOptionals ในประเภทค่า Java 10 อาจลดหรือลบบทลงโทษเพิ่มเติม

การทำให้เป็นอันดับ

Optionalไม่ต่อเนื่องแต่การแก้ปัญหาไม่ซับซ้อนเกินไป

ไม่แปรเปลี่ยน

เนื่องจากความไม่แน่นอนของประเภททั่วไปใน Java การดำเนินการบางอย่างกลายเป็นเรื่องยุ่งยากเมื่อชนิดของค่าที่แท้จริงถูกผลักเข้าไปในอาร์กิวเมนต์ประเภททั่วไป ตัวอย่างที่จะได้รับที่นี่ (ดู "polymorphism Parametric")


ข้อเสียอีกประการหนึ่ง (หรือข้อ จำกัด ) คือคุณไม่สามารถจัดเรียงอินสแตนซ์เผื่อเลือก (xxx) ได้แม้ว่า Oracle จะกำหนดข้อ จำกัด ดังกล่าวอย่างชัดเจนว่าจงใจ ฉันสงสัยเกี่ยวกับภูมิปัญญาของการมีชั้นเรียนเหล่านี้ที่พวกเขาควรจะใช้เฉพาะในสถานการณ์ที่เฉพาะเจาะจงและสมมติว่านักพัฒนาจะทำสิ่งที่ถูกต้อง การแก้ปัญหาหนึ่งอาจทำให้เกิดปัญหาอื่นได้ อาจมีคำเตือนคอมไพเลอร์เพื่อช่วยผู้พัฒนาเมื่อใช้คลาสเสริม (xxx) ในรูปแบบ "ไม่พึงประสงค์" (เช่นพารามิเตอร์วิธี, ในคอลเลกชัน, ในตัวสร้าง ฯลฯ ) ถ้ามันไม่ได้ตั้งใจใช้จริง ๆ ?
skomisa

เกี่ยวกับ 4 ตัวเลือกในคอลเลกชัน: บางครั้งมีประโยชน์ในการจัดเก็บตัวเลือกในLists นี่เป็นกรณีที่มีความสำคัญที่องค์ประกอบบางอย่างจะอยู่ที่ดัชนีหนึ่ง แต่องค์ประกอบนั้นสามารถมีอยู่ได้หรือไม่ นั่นคือการสื่อสารอย่างชัดเจนตามList<Optional<T>>ประเภท หากในอีกทางหนึ่งมันเป็นสิ่งสำคัญเท่านั้นที่วัตถุที่เป็นสมาชิกบางคอลเลกชันบางส่วนแล้วไม่มีจุด
Lii

3
ฉันคิดว่า use-case # 2 ยังน่าสงสัยอยู่มาก ถูกส่งผ่านเข้าสู่วิธีการที่สามารถOptional nullคุณได้อะไรมา? ตอนนี้คุณมีสองเช็คที่จะทำให้ ดูstackoverflow.com/a/31923042/650176
David V

2
@DavidV ดูรายการของข้อได้เปรียบที่ฉันให้ - พวกเขาทั้งหมดนำไปใช้กับกรณีนี้เช่นกัน นอกจากนี้หากคุณดำเนินการต่อไปOptional(ซึ่งควรเป็นกรณีหากคุณยินดีที่จะผ่านมันเป็นอาร์กิวเมนต์) nullจะไม่เป็นค่าทางกฎหมาย ดังนั้นการเรียกเมธอดที่มีพารามิเตอร์ที่ชัดเจนnullจึงเป็นความผิดพลาดที่เห็นได้ชัด
Nicolai

28

โดยส่วนตัวแล้วฉันชอบที่จะใช้เครื่องมือตรวจสอบรหัสของ IntelliJเพื่อใช้@NotNullและ@Nullableตรวจสอบเนื่องจากส่วนใหญ่เป็นเวลารวบรวม (สามารถมีการตรวจสอบรันไทม์บางส่วน) สิ่งนี้มีค่าใช้จ่ายที่ต่ำกว่าในแง่ของความสามารถในการอ่านรหัส ไม่เข้มงวดเท่าการใช้ตัวเลือกอย่างไรก็ตามการขาดความแม่นยำนี้ควรได้รับการสนับสนุนจากการทดสอบหน่วยที่เหมาะสม

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

ใช้งานได้กับ Java 5 และไม่จำเป็นต้องห่อและแกะค่า (หรือสร้างวัตถุห่อหุ้ม)


7
อืม, คำอธิบายประกอบของ IDEA เหล่านั้นคืออะไร? ฉันชอบ JSR 305 โดย@Nonnullส่วนตัว - ทำงานร่วมกับ FindBugs;)
fge

@fge มีการขาดมาตรฐานในเรื่องนี้ แต่ผมเชื่อว่าเครื่องมือที่มักจะมีการกำหนดค่าและคุณจะได้ไม่ต้องจบลงด้วย@Nonnull @NotNull etc
ปีเตอร์ Lawrey

4
ใช่มันเป็นเรื่องจริง IDEA (13.x) มีสามตัวเลือกให้เลือก ... Meh, ฉันมักจะใช้ JSR 305 anywa
fge

3
ฉันรู้ว่ามันเก่า แต่การอภิปรายเกี่ยวกับคำอธิบายประกอบ & เครื่องมือสมควรได้รับลิงก์ไปยังstackoverflow.com/questions/35892063// - btw ใดที่ระบุถึงกรณีของ Java 8 เป็นพิเศษ
Stephan Herrmann

25

ฉันคิดว่าตัวเลือก Guava และหน้า wiki ของพวกเขาค่อนข้างดี:

นอกเหนือจากการเพิ่มความสามารถในการอ่านที่มาจากการตั้งชื่อให้เป็นโมฆะประโยชน์ที่ใหญ่ที่สุดของตัวเลือกคือความเป็นคนงี่เง่าที่พิสูจน์ได้ มันบังคับให้คุณคิดอย่างแข็งขันเกี่ยวกับคดีที่หายไปหากคุณต้องการให้โปรแกรมของคุณรวบรวมเลยเนื่องจากคุณต้องแกะตัวเลือกและที่อยู่กรณีนั้นอย่างแข็งขัน Null ทำให้ง่ายต่อการลืมสิ่งต่าง ๆ ได้ง่ายและแม้ว่า FindBugs จะช่วยได้เราก็ไม่คิดว่ามันจะแก้ปัญหาได้เช่นกัน

สิ่งนี้มีความเกี่ยวข้องโดยเฉพาะอย่างยิ่งเมื่อคุณส่งคืนค่าที่อาจมีหรือไม่มี "อยู่" คุณ (และคนอื่น ๆ ) มีแนวโน้มที่จะลืมว่าวิธีการอื่น (a, b) อาจส่งคืนค่า Null ได้มากกว่าที่คุณจะลืมว่า a อาจเป็นโมฆะเมื่อคุณใช้วิธีอื่น การคืนค่าเป็นตัวเลือกทำให้ผู้โทรไม่สามารถลืมกรณีดังกล่าวได้เนื่องจากพวกเขาต้องแกะวัตถุเองเพื่อให้คอมไพล์รหัสของตน - (ที่มา: Guava Wiki - การใช้และการหลีกเลี่ยงค่าว่าง - มีประเด็นอะไร )

Optionalเพิ่มค่าใช้จ่ายบางอย่าง แต่ฉันคิดว่าข้อได้เปรียบที่ชัดเจนคือการทำให้ชัดเจน ว่าวัตถุอาจหายไปและบังคับให้โปรแกรมเมอร์เขียนโปรแกรมรับมือกับสถานการณ์ เป็นการป้องกันไม่ให้คนอื่นลืมการ!= nullตรวจสอบอันเป็นที่รัก

จากตัวอย่างที่2ฉันคิดว่ามันเป็นโค้ดที่ชัดเจนกว่าที่จะเขียน:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

กว่า

if(soundcard != null){
  System.out.println(soundcard);
}

สำหรับฉันOptionalดีกว่ารวบรวมความจริงที่ว่าไม่มีการ์ดเสียงอยู่

2 ¢ของฉันเกี่ยวกับคะแนนของคุณ:

  1. public Optional<Foo> findFoo(String id);- ฉันไม่แน่ใจเกี่ยวกับเรื่องนี้ บางทีผมอาจจะกลับมาResult<Foo>ซึ่งอาจจะเป็นที่ว่างเปล่าFooหรือมี มันเป็นแนวคิดที่คล้ายกัน Optionalแต่ไม่ได้จริงๆ
  2. public Foo doSomething(String id, Optional<Bar> barOptional);- ฉันต้องการ @Nullable และตรวจสอบ findbugs ตามคำตอบของ Peter Lawrey - ดูการสนทนานี้
  3. ตัวอย่างหนังสือของคุณ - ฉันไม่แน่ใจว่าฉันจะใช้ตัวเลือกภายในหรือไม่นั้นอาจขึ้นอยู่กับความซับซ้อน สำหรับ "API" ของหนังสือฉันจะใช้Optional<Index> getIndex()เพื่อระบุอย่างชัดเจนว่าหนังสืออาจไม่มีดัชนี
  4. ฉันจะไม่ใช้มันในคอลเลกชัน แต่ไม่อนุญาตให้มีค่า null ในคอลเลกชัน

โดยทั่วไปแล้วผมจะพยายามที่จะลดการผ่านรอบnulls (เมื่อถูกไฟไหม้ ... ) ฉันคิดว่ามันคุ้มค่าที่จะหา abstractions ที่เหมาะสมและบ่งบอกถึงโปรแกรมเมอร์คนอื่น ๆ ว่าค่าตอบแทนที่แท้จริงนั้นหมายถึงอะไร


15
soundcard.ifPresent(System.out::println)คุณจะเขียน โทรเป็นความหมายเช่นเดียวกับการตรวจสอบisPresent null
ที่ดีกว่า

ปัญหาสำหรับฉันคือสิ่งต่าง ๆ ที่มีประเภทเป็นตัวเลือกยังคงเป็นโมฆะ if(soundcard != null && soundcard.isPresent())ดังนั้นจะต้องมีความปลอดภัยอย่างสมบูรณ์จริงที่คุณต้องทำสิ่งที่ชอบ แม้ว่าการทำ API ที่คืนค่าเผื่อเลือกและอาจคืนค่าว่างเปล่าเป็นสิ่งที่ฉันหวังว่าจะไม่มีใครทำ
Simon Baumgardt-Wellander

"มันบังคับให้คุณคิดอย่างแข็งขันเกี่ยวกับคดีที่หายไปถ้าคุณต้องการให้โปรแกรมของคุณคอมไพล์เลยเพราะคุณต้องแกะตัวเลือกและที่อยู่กรณีนั้นออกมา" ฉันไม่แน่ใจจริงๆว่ามันบังคับอย่างไรแม้ว่าฉันจะไม่ได้ทำงานในจาวามากนัก คุณช่วยอธิบายว่า Java ทำอะไรเพื่อบังคับให้คุณแกะและตรวจสอบเคส! isPresent เพื่อคอมไพล์?
สนใจ

15

จากบทช่วยสอนของ Oracle :

วัตถุประสงค์ของตัวเลือกไม่ได้เป็นการแทนที่การอ้างอิงโมฆะทุกครั้งใน codebase ของคุณ แต่จะช่วยออกแบบ API ที่ดีกว่าโดยเพียงแค่อ่านลายเซ็นของวิธีการ - ผู้ใช้สามารถบอกได้ว่าจะคาดหวังค่าที่เป็นทางเลือกหรือไม่ นอกจากนี้ทางเลือกบังคับให้คุณทำการแกะตัวเลือกเพื่อจัดการกับการไม่มีค่า เป็นผลให้คุณป้องกันรหัสของคุณจากข้อยกเว้นตัวชี้โมฆะที่ไม่ได้ตั้งใจ


3
API สำหรับส่วนส่งคืนของวิธีการเท่านั้นใช่ไหม ไม่ควรใช้ตัวเลือกเป็นพารามิเตอร์เนื่องจากการลบประเภท? บางครั้งฉันใช้ตัวเลือกภายในในวิธีการแปลงจากพารามิเตอร์ nullable หรือเป็นสนามเริ่มต้นจากพารามิเตอร์ตัวสร้างโมฆะ ... ยังคงพยายามที่จะคิดออกว่าเป็นประโยชน์มากกว่าเพียงแค่ปล่อยให้มันเป็นโมฆะ - ใครมีความคิดใด ๆ ?
ycomp

@ycomp คุณสามารถค้นหาข้อมูลมากค่อนข้างเกี่ยวกับกรณีผู้ที่อยู่ในนี้หรือว่าคำถาม คำตอบสำหรับคำถามเหล่านั้นบอกได้มากกว่าสิ่งที่ถามและครอบคลุมกรณีอื่น ๆ สำหรับข้อมูลเพิ่มเติมเรียกดูเพิ่มเติมหรือถามคำถามของคุณเองเพราะคุณอาจไม่ได้รับความสนใจมากนักที่นี่ ; )
ctomek

8

1 - ในฐานะที่เป็นวิธีการคืนค่าแบบสาธารณะเมื่อเมธอดสามารถส่งคืนค่า null:

นี่คือบทความที่ดีที่แสดงประโยชน์ของ usecase # 1 มีรหัสนี้

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

ถูกแปลงเป็นสิ่งนี้

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

โดยใช้ทางเลือกเป็นค่าตอบแทนของวิธีการทะเยอทะยานที่เกี่ยวข้อง


2

นี่คือการใช้งานที่น่าสนใจ (ฉันเชื่อว่า) สำหรับ ... การทดสอบ

ฉันตั้งใจจะทดสอบโครงการของฉันอย่างหนักและสร้างการยืนยัน มีเพียงสิ่งที่ฉันต้องยืนยันและอื่น ๆ ที่ฉันทำไม่ได้

ฉันจึงสร้างสิ่งต่าง ๆ เพื่อยืนยันและใช้การยืนยันเพื่อตรวจสอบพวกเขาเช่นนี้

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

ในคลาส NodeAssert ฉันทำสิ่งนี้:

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

ซึ่งหมายความว่าการยืนยันนั้นเป็นต้นเหตุถ้าฉันต้องการตรวจสอบฉลากเท่านั้น!


2

Optionalคลาสช่วยให้คุณหลีกเลี่ยงการใช้nullและมอบทางเลือกที่ดีกว่า:

  • สิ่งนี้สนับสนุนให้นักพัฒนาทำการตรวจสอบสถานะเพื่อหลีกเลี่ยงการไม่NullPointerExceptionถูกตรวจจับ

  • API กลายเป็นเอกสารที่ดีขึ้นเพราะเป็นไปได้ที่จะเห็นว่าจะคาดหวังค่าที่สามารถหายไปได้ที่ไหน

Optionalให้ API ที่สะดวกสำหรับการทำงานกับวัตถุต่อไป: isPresent(); get(); orElse(); orElseGet(); orElseThrow(); map(); filter(); flatmap().

นอกจากนี้กรอบงานจำนวนมากใช้ประเภทข้อมูลนี้และส่งคืนจาก API ของพวกเขา


2

ในจาวาอย่าใช้มันจนกว่าคุณจะติดโปรแกรมการทำงาน

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

พวกเขาสมเหตุสมผลสำหรับค่าส่งคืน แต่พวกเขาเชิญคลาสไคลเอ็นต์เพื่อขยายห่วงโซ่การสร้างพฤติกรรม

FP และเชนส์มีสถานที่เล็ก ๆ ในภาษาที่จำเป็นอย่างเช่นจาวาเพราะมันทำให้ยากต่อการดีบักไม่ใช่แค่อ่าน เมื่อคุณก้าวเข้าสู่เส้นคุณจะไม่ทราบสถานะหรือเจตนาของโปรแกรม คุณต้องก้าวเข้าสู่การคิดออก (เป็นรหัสที่มักไม่ใช่ของคุณและเฟรมสแต็กจำนวนมากแม้จะมีตัวกรองขั้นตอน) และคุณต้องเพิ่มจุดพักมากมายเพื่อให้แน่ใจว่าสามารถหยุดในโค้ด / แลมบ์ดาที่คุณเพิ่ม แทนที่จะเดินเพียงแค่ if / else / call บรรทัดเล็ก ๆ น้อย ๆ

หากคุณต้องการใช้งานฟังก์ชั่นการเขียนโปรแกรมให้เลือกอย่างอื่นที่ไม่ใช่จาวาและหวังว่าคุณจะมีเครื่องมือสำหรับการดีบัก


1

ฉันไม่คิดว่าเป็นตัวเลือกแทนทั่วไปสำหรับวิธีการที่อาจส่งกลับค่าเป็นศูนย์

แนวคิดพื้นฐานคือ: การไม่มีค่าไม่ได้หมายความว่าอาจมีอยู่ในอนาคต เป็นความแตกต่างระหว่าง findById (-1) และ findById (67)

ข้อมูลหลักของ Optionals สำหรับผู้โทรคือเขาอาจไม่ได้คิดตามมูลค่าที่กำหนด แต่อาจมีอยู่ในบางครั้ง บางทีมันอาจจะหายไปอีกครั้งและกลับมาอีกครั้งในภายหลัง มันเหมือนสวิตช์เปิด / ปิด คุณมี "ตัวเลือก" เพื่อเปิดหรือปิดไฟ แต่คุณไม่มีทางเลือกถ้าคุณไม่มีไฟให้เปิด

ดังนั้นฉันคิดว่ามันยุ่งเกินกว่าที่จะแนะนำ Optionals ทุกที่ที่อาจมีการส่งคืนค่าว่างก่อนหน้านี้ ฉันจะยังคงใช้ null แต่เฉพาะในพื้นที่ จำกัด เช่นรากของต้นไม้เริ่มต้นขี้เกียจและวิธีการค้นหาที่ชัดเจน


0

ดูเหมือนว่าOptionalจะเป็นประโยชน์เฉพาะในกรณีที่ประเภท T ในตัวเลือกเป็นชนิดดั้งเดิมเช่นint, long, charฯลฯ สำหรับการเรียน "ของจริง" ก็ไม่ได้ทำให้ความรู้สึกที่ฉันเป็นคุณสามารถใช้nullค่าอยู่แล้ว

ฉันคิดว่ามันมาจากที่นี่ (หรือจากแนวคิดภาษาอื่นที่คล้ายคลึงกัน)

Nullable<T>

ใน C # นี้Nullable<T>ถูกนำมาใช้นานแล้วเพื่อห่อประเภทค่า


2
Optionalในความเป็นจริงแล้วมันเป็นจุดเริ่มต้นของ Guava
fge

2
@ ตกลงตกลง แต่เมื่อสิ่งนี้มีอยู่ใน C # (2005, MS.NET 2.0) ไม่มี Guava ฉันคิดว่า และใครจะรู้ว่า C # นำสิ่งนี้มาจากไหน
peter.petrov

3
@fge "กันหาของฝรั่งของตัวเลือก" java.util.Optionalเป็นชนิดของทางตลกที่จะนำมันตั้งแต่พวกฝรั่งมีส่วนร่วมในการอภิปรายให้ประสบการณ์ของพวกเขาและโดยทั่วไปอยู่ในความโปรดปรานของ
Stuart Marks

6
@fge ไม่ต้องสงสัยเลยว่า API ของ Java ได้รับอิทธิพลอย่างมากจาก Guava ฉันกำลังมีปัญหากับ "ripoff" ซึ่งดูเหมือนจะขโมย พวก Guava มีส่วนร่วมในความคิดที่มีค่ามากมายสำหรับ Java 8
Stuart Marks

6
คุณไม่มีจุด ทางเลือกจะใช้แทนการส่งคืน null ปลอดภัยกว่าการขว้าง NullPointerException ดู: oracle.com/technetwork/articles/java/…
Kilizo

0

มีความหมายคล้ายกับอินสแตนซ์ unmodifiable ของรูปแบบการออกแบบ Iterator :Optional

  • มันอาจหรือไม่อาจอ้างถึงวัตถุ (ตามที่กำหนดโดย isPresent() )
  • มันสามารถถูกget()อ้างถึง(ใช้) ถ้ามันอ้างถึงวัตถุ
  • แต่ไม่สามารถก้าวไปยังตำแหน่งต่อไปในลำดับได้ (ไม่มีnext()วิธี)

ดังนั้นให้พิจารณาการส่งคืนหรือส่งผ่านOptionalบริบทที่คุณอาจเคยพิจารณาใช้ Java มาIteratorก่อน


-1

Java SE 8 แนะนำคลาสใหม่ที่เรียกว่า java.util.Optional

คุณสามารถสร้างตัวเลือกที่ว่างเปล่าหรือตัวเลือกที่มีค่าเป็นศูนย์

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

และนี่คือตัวเลือกที่มีค่าที่ไม่ใช่ค่าว่าง:

String valueString = new String("TEST");
Optional<String> optinalValueString = Optional.of(valueString );

ทำอะไรสักอย่างถ้ามีคุณค่า

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

String nullString = null;
if (nullString != null) {
    System.out.println(nullString);
}

คุณสามารถใช้วิธี ifPresent () ดังต่อไปนี้:

Optional<String> optinalString= null;
optinalString.ifPresent(System.out::println);

package optinalTest;

import java.util.Optional;

public class OptionalTest {
    public Optional<String> getOptionalNullString() {
        return null;
//      return Optional.of("TESt");
    }

    public static void main(String[] args) {

        OptionalTest optionalTest = new OptionalTest();

        Optional<Optional<String>> optionalNullString = Optional.ofNullable(optionalTest.getOptionalNullString());

        if (optionalNullString.isPresent()) {
            System.out.println(optionalNullString.get());
        }
    }
}

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