Java 8 getters ส่งคืนชนิดตัวเลือกหรือไม่?


288

Optional type ที่แนะนำใน Java 8 เป็นสิ่งใหม่สำหรับนักพัฒนาหลายคน

เป็นวิธีการทะเยอทะยานกลับOptional<Foo>ประเภทในสถานที่ของคลาสสิกFooการปฏิบัติที่ดี? nullสมมติว่าค่าที่สามารถ


8
แม้ว่าจะมีแนวโน้มที่จะดึงดูดคำตอบที่ให้ความเห็น แต่เป็นคำถามที่ดี ฉันรอคอยที่จะได้คำตอบพร้อมข้อเท็จจริงที่แท้จริงในเรื่องนี้
Justin

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

1
ดูการอภิปรายเกี่ยวกับ@NotNullคำอธิบายประกอบ: stackoverflow.com/q/4963300/873282
koppor

คำตอบ:


517

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

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

ฉันคิดว่าการใช้เป็นประจำเป็นค่าตอบแทนสำหรับผู้ได้รับจะใช้มากเกินไป

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

(ประกาศบริการสาธารณะ: ไม่เคยโทรOptional.getจนกว่าคุณจะสามารถพิสูจน์ได้ว่ามันจะไม่เป็นโมฆะแทนใช้หนึ่งในวิธีการที่ปลอดภัยเช่นorElseหรือifPresent. ในการหวนกลับเราควรจะได้เรียกว่าgetสิ่งที่ชอบgetOrElseThrowNoSuchElementExceptionหรือสิ่งที่ทำให้มันไกลชัดเจนว่านี่เป็นวิธีที่อันตรายอย่างยิ่ง ที่ทำลายวัตถุประสงค์ทั้งหมดของOptionalในสถานที่แรกบทเรียนที่ได้เรียนรู้ (อัพเดท: Java 10 มีOptional.orElseThrow()ซึ่งเทียบเท่ากับความหมายget()แต่มีชื่อที่เหมาะสมกว่า)


24
(เกี่ยวกับส่วนสุดท้าย) ... และเมื่อเรามีความมั่นใจคุณค่าที่nullเราไม่เคยใช้orElseThrow(AssertionError::new)อะแฮ่มหรือorElseThrow(NullPointerException::new)
โฮลเกอร์

25
สิ่งที่คุณจะได้ทำแตกต่างกันถ้าความตั้งใจของคุณได้รับการแนะนำวัตถุประสงค์ทั่วไปบางทีหรือบางประเภท? มีวิธีการที่ตัวเลือกไม่เหมาะกับการเรียกเก็บเงินหรือไม่หรือเป็นเพียงการแนะนำตัวเลือกที่มีอยู่ทั่ว API ใหม่จะทำให้ไม่ใช่แบบจาวาหรือไม่
David Moles

22
โดย "ประเภทวัตถุประสงค์ทั่วไป" ฉันหมายถึงการสร้างมันลงในระบบประเภทของภาษาแทนที่จะให้ชั้นห้องสมุดที่ใกล้เคียงกับมัน (บางภาษามีประเภทสำหรับ T? (T หรือ Null) และ T! (ไม่ใช่ T nullable)) ทางเลือกเป็นเพียงคลาส เราไม่สามารถทำการแปลงโดยนัยระหว่าง Foo และ <Foo> ที่เป็นทางเลือกได้เนื่องจากเราสามารถใช้ภาษาได้
Brian Goetz

11
ฉันสงสัยในขณะที่เหตุผลที่เน้นในโลก Java เป็นทางเลือกและไม่วิเคราะห์ดีกว่าคงที่ ตัวเลือกจะมีข้อดีบางอย่าง แต่ข้อดีมากที่nullมีความเข้ากันได้ย้อนหลัง; Map::getส่งกลับค่า nullable Vไม่ใช่ an Optional<V>และจะไม่มีการเปลี่ยนแปลง มันจะได้รับการบันทึกย่อได้อย่างง่ายดาย@Nullableแม้ว่า ตอนนี้เรามีสองวิธีในการแสดงถึงการขาดคุณค่าและมีแรงจูงใจน้อยลงเพื่อให้เกิดการวิเคราะห์แบบคงที่ซึ่งดูเหมือนจะเป็นตำแหน่งที่เลวร้ายกว่าที่จะเกิดขึ้น
yshavit

21
ฉันไม่รู้ว่าการใช้มันเป็นค่าส่งคืนสำหรับคุณสมบัติไม่ใช่สิ่งที่คุณตั้งใจจนกว่าฉันจะอ่านคำตอบนี้ที่ StackOverflow ที่จริงแล้วฉันเชื่อว่ามันเป็นสิ่งที่ y'all ตั้งใจหลังจากอ่านบทความนี้ในเว็บไซต์ของ Oracle: oracle.com/technetwork/articles/java/ ขอขอบคุณสำหรับการชี้แจง
Jason Thompson

73

หลังจากทำวิจัยของฉันเองฉันก็เจอสิ่งต่าง ๆ ที่อาจแนะนำเมื่อสิ่งนี้เหมาะสม ผู้มีอำนาจมากที่สุดเป็นคำพูดต่อไปนี้จากบทความ Oracle:

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

ฉันยังพบข้อความที่ตัดตอนมาจากJava 8 ตัวเลือกนี้: วิธีใช้

"ตัวเลือกไม่ได้มีไว้สำหรับใช้ในบริบทเหล่านี้เนื่องจากจะไม่ซื้อสิ่งใด ๆ กับเรา:

  • ในเลเยอร์โมเดลโดเมน (ไม่สามารถปรับแต่งได้)
  • ใน DTOs (เหตุผลเดียวกัน)
  • ในพารามิเตอร์อินพุตของเมธอด
  • ในพารามิเตอร์ตัวสร้าง "

ซึ่งดูเหมือนว่าจะเพิ่มคะแนนที่ถูกต้องบางอย่าง

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


11
stackoverflow.com/questions/25693309 - ดูเหมือนว่าแจ็คสันสนับสนุนแล้วดังนั้น "ไม่ต่อเนื่อง" ไม่ผ่านเป็นเหตุผลที่ถูกต้องอีกต่อไป :)
Vlasec

1
ฉันขอแนะนำให้มีที่อยู่คำตอบของคุณว่าทำไมใช้Optionalในพารามิเตอร์อินพุตของวิธีการ (โดยเฉพาะอย่างยิ่งคอนสตรัคเตอร์) "จะไม่ซื้ออะไรเลย" dolszewski.com/java/java-8-optional-use-casesมีคำอธิบายที่ดี
Gili

ฉันพบว่าการปิดกั้นผลลัพธ์ที่เป็นตัวเลือกซึ่งจำเป็นต้องแปลงเป็น API อื่นต้องใช้พารามิเตอร์เผื่อเลือก ผลลัพธ์เป็น API ที่เข้าใจได้อย่างเป็นธรรม ดูstackoverflow.com/a/31923105/105870
Karl the Pagan

1
ลิงก์เสีย คุณช่วยอัพเดทคำตอบได้ไหม?
softarn

2
ปัญหาคือมันมักจะไม่ปรับปรุง API แม้จะมีความตั้งใจที่ดีที่สุดของนักพัฒนา ฉันได้รับตัวอย่างของการใช้งานที่ไม่ดีของตัวเลือกทั้งหมดนำมาจากรหัสการผลิตซึ่งคุณสามารถตรวจสอบได้
MiguelMunoz

20

ฉันว่าโดยทั่วไปแล้วมันเป็นความคิดที่ดีที่จะใช้ประเภทที่เป็นตัวเลือกสำหรับค่าส่งคืนที่สามารถเป็นโมฆะได้ อย่างไรก็ตาม wrt to frameworks ฉันคิดว่าการแทนที่ getters แบบคลาสสิกด้วยประเภทที่เป็นตัวเลือกจะทำให้เกิดปัญหามากเมื่อทำงานกับ frameworks (เช่น Hibernate) ที่อาศัยการเข้ารหัสแบบแผนการสำหรับ getters และ setters


14
คำแนะนำนี้เป็นสิ่งที่จัดเรียงของสิ่งที่ฉันหมายโดย "เรามีความกังวลเกี่ยวกับความเสี่ยงของการใช้มากเกินไปกระตือรือร้นว่า" ในstackoverflow.com/a/26328555/3553087
Brian Goetz

13

เหตุผลที่Optionalถูกเพิ่มลงใน Java เป็นเพราะ:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

สะอาดกว่านี้:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

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

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

ฉันคิดว่ามีความกระหายที่แท้จริงในการแก้ไขข้อบกพร่องนี้เพราะผู้คนจำนวนมากที่เห็นคลาสตัวเลือกใหม่สันนิษฐานว่าจุดประสงค์ของมันคือการเพิ่มความชัดเจนให้กับ API ทำไมคนถามคำถามเช่น "ควร getters คืน Optionals?" ไม่พวกเขาอาจไม่ควรเว้นเสียแต่ว่าคุณคาดหวังให้ผู้ใช้ทะเยอทะยานมาใช้ในการเขียนโปรแกรมการทำงานซึ่งไม่น่าเป็นไปได้มากนัก ในความเป็นจริงถ้าคุณดูที่ใช้เป็นตัวเลือกใน Java API นั้นส่วนใหญ่จะอยู่ในชั้นเรียนสตรีมซึ่งเป็นแกนหลักของการเขียนโปรแกรมการทำงาน (ฉันไม่ได้ตรวจสอบอย่างละเอียดมาก แต่คลาส Stream อาจเป็นเพียงคลาสเดียวเท่านั้นที่ใช้)

หากคุณวางแผนที่จะใช้ getter ในบิตของรหัสการทำงานมันอาจเป็นความคิดที่ดีที่จะมี getter มาตรฐานและอีกอันหนึ่งที่ส่งกลับเป็นตัวเลือก

โอ้และถ้าคุณต้องการให้คลาสของคุณเป็นแบบอนุกรมคุณไม่ควรใช้ตัวเลือกอย่างแน่นอน

ตัวเลือกเป็นวิธีแก้ปัญหาข้อบกพร่อง API ที่ไม่ดีนักเนื่องจากก) พวกเขามีรายละเอียดมากและข) พวกเขาไม่เคยตั้งใจจะแก้ไขปัญหานั้นตั้งแต่แรก

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

สิ่งนี้เปลี่ยนแปลงทุกสิ่ง

สำหรับผู้ได้รับของคุณอย่าใช้ทางเลือก และพยายามออกแบบชั้นเรียนของคุณเพื่อไม่ให้สมาชิกคนใดเป็นโมฆะ และอาจลองเพิ่ม Nullness Checker ในโครงการของคุณและประกาศ getters และพารามิเตอร์ setter ของคุณ @Nullable หากต้องการ ฉันทำสิ่งนี้กับโครงการใหม่เท่านั้น มันอาจสร้างคำเตือนจำนวนมากในโครงการที่มีอยู่ซึ่งเขียนด้วยการทดสอบฟุ่มเฟือยมากมายสำหรับโมฆะดังนั้นจึงอาจเป็นเรื่องยากที่จะติดตั้งเพิ่มเติม แต่มันก็จะจับข้อบกพร่องมากมาย ฉันรักมัน. รหัสของฉันสะอาดกว่าและเชื่อถือได้มากกว่าเพราะมัน

(นอกจากนี้ยังมีภาษาใหม่ที่ระบุสิ่งนี้ Kotlin ซึ่งรวบรวมรหัส Java byte ช่วยให้คุณระบุว่าวัตถุอาจเป็นโมฆะเมื่อคุณประกาศมันเป็นวิธีที่สะอาดขึ้น)

ภาคผนวกในโพสต์ดั้งเดิม (รุ่น 2)

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

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

ต้องบอกว่าทั้งหมดที่ฉันยังคงเห็นวิธีการที่ใช้โดยNullness Checkerเป็นวิธีที่ดีที่สุดในการจัดการกับ nulls เนื่องจากพวกเขาเปิด NullPointerExceptions จากข้อบกพร่อง runtime เพื่อรวบรวมข้อผิดพลาดเวลา


2
คำตอบนี้ดูเหมือนจะเหมาะสมที่สุดสำหรับฉัน ทางเลือกถูกเพิ่มใน java 8 เมื่อเพิ่มสตรีมแล้วเท่านั้น และฟังก์ชั่นสตรีมเท่านั้นที่คืนค่าตัวเลือกเท่าที่ฉันเคยเห็น
Archit

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

3

หากคุณใช้ serializers ที่ทันสมัยและกรอบอื่น ๆ ที่เข้าใจOptionalแล้วฉันได้พบแนวทางเหล่านี้ทำงานได้ดีเมื่อเขียนEntityถั่วและเลเยอร์โดเมน:

  1. ถ้าเลเยอร์การทำให้เป็นอนุกรม (โดยปกติคือฐานข้อมูล) อนุญาตให้nullค่าสำหรับเซลล์ในคอลัมน์BARในตารางFOOได้ดังนั้นผู้Foo.getBar()เรียกสามารถส่งคืนการOptionalบ่งชี้ไปยังผู้พัฒนาได้ว่าค่านี้อาจคาดว่าสมเหตุสมผล ถ้า DB รับประกันค่าจะไม่เป็นโมฆะแล้วทะเยอทะยานควรไม่Optionalห่อนี้ในการ
  2. Foo.barควรจะเป็นprivateและไม่ได้Optionalเป็น มีจริงๆเหตุผลที่ว่ามันจะไม่มีถ้ามันเป็นOptionalprivate
  3. หมาFoo.setBar(String bar)ควรใช้ชนิดของbarและไม่ได้ Optionalหากตกลงเพื่อใช้nullอาร์กิวเมนต์ให้ระบุสิ่งนี้ในความคิดเห็น JavaDoc หากยังไม่ได้ตกลงที่จะใช้หรือตรรกะทางธุรกิจบางอย่างที่เหมาะสมคือ IMHO ที่เหมาะสมมากขึ้นnullIllegalArgumentException
  4. ตัวสร้างไม่จำเป็นต้องมีOptionalอาร์กิวเมนต์ (ด้วยเหตุผลคล้ายกับจุดที่ 3) โดยทั่วไปฉันมีเพียงข้อโต้แย้งในตัวสร้างที่จะต้องไม่เป็นโมฆะในฐานข้อมูลอนุกรม

เพื่อให้การดังกล่าวข้างต้นที่มีประสิทธิภาพมากขึ้นคุณอาจต้องการแก้ไขแม่แบบ IDE ของคุณสำหรับการสร้าง getters และแม่แบบที่สอดคล้องกันสำหรับtoString(), equals(Obj o)ฯลฯ หรือสาขาที่ใช้โดยตรงสำหรับผู้ที่ (ส่วนใหญ่กำเนิด IDE แล้วจัดการกับ nulls)

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