คำนำ
พอยน์เตอร์โมฆะเป็นข้อยกเว้นที่พบบ่อยที่สุดและน่ารำคาญที่เราเกลียด เพื่อป้องกันไม่ให้พอยน์เตอร์เป็นโมฆะคุณต้องไม่เขียนการตัดสินที่ไม่ใช่ของ Null จำนวนมากในรหัสของคุณ
Java 8 แนะนำคลาสเสริมใหม่ เพื่อหลีกเลี่ยงการเกิดขึ้นของพอยน์เตอร์โมฆะไม่จำเป็นต้องเขียนคำตัดสิน if(obj!=null) ตราบใดที่คุณต้องโหลดข้อมูลเป็นทางเลือกมันเป็นคอนเทนเนอร์ที่ห่อวัตถุ
ว่ากันว่าไม่มีโปรแกรมเมอร์ที่พบข้อยกเว้นตัวชี้โมฆะไม่ใช่โปรแกรมเมอร์ Java และ Null ได้ก่อให้เกิดปัญหามากมาย Java 8 แนะนำคลาสใหม่ที่เรียกว่า java.util.Optional เพื่อหลีกเลี่ยงปัญหามากมายที่เกิดจาก Null
มาดูกันว่าการอ้างอิงโมฆะเป็นอันตรายอะไร ก่อนสร้างคอมพิวเตอร์คลาสโครงสร้างจะแสดงในรูปด้านล่าง:
จะเกิดอะไรขึ้นเมื่อเราเรียกรหัสต่อไปนี้
สตริงเวอร์ชัน = คอมพิวเตอร์ getSoundCard (). getUSB (). getVersion ();
รหัสข้างต้นดูเหมือนจะไม่เป็นไร แต่คอมพิวเตอร์หลายเครื่อง (เช่น Raspberry Pi) ไม่มีการ์ดเสียงดังนั้นการเรียกใช้วิธีการ getSoundcard() จะทำให้เกิดข้อยกเว้นตัวชี้โมฆะอย่างแน่นอน
วิธีปกติ แต่ไม่ดีคือการส่งคืนการอ้างอิง NULL เพื่อระบุว่าคอมพิวเตอร์ไม่มีการ์ดเสียง แต่นี่หมายความว่าวิธีการ GetUSB () จะถูกเรียกใช้การอ้างอิงที่ว่างเปล่าซึ่งจะทำให้เกิดข้อยกเว้นการควบคุมในระหว่างการทำงานของโปรแกรมทำให้โปรแกรมหยุดทำงาน ลองคิดดูสิว่ามันน่าอายแค่ไหนที่จะปรากฏขึ้นอย่างกะทันหันเมื่อโปรแกรมของคุณทำงานบนคอมพิวเตอร์ไคลเอนต์?
วิทยาศาสตร์คอมพิวเตอร์ที่ยอดเยี่ยม Tony Hoare เคยเขียนว่า: "ฉันคิดว่าการอ้างอิงแบบว่างถูกสร้างขึ้นในปี 2508 ซึ่งส่งผลให้เกิดการสูญเสียเป็นพันล้านดอลลาร์การล่อลวงครั้งใหญ่ที่สุดสำหรับฉันเมื่อใช้การอ้างอิงโมฆะคือมันง่ายที่จะนำไปใช้"
ดังนั้นเราจะหลีกเลี่ยงข้อยกเว้นตัวชี้ว่างได้อย่างไรเมื่อโปรแกรมทำงาน คุณต้องตื่นตัวและตรวจสอบพอยน์เตอร์ว่างที่เป็นไปได้อย่างต่อเนื่องเช่นนี้:
สตริงเวอร์ชัน = "ไม่ทราบ"; if (คอมพิวเตอร์! = null) {SoundCard SoundCard = Computer.getSoundCard (); if (soundcard! = null) {usb usb = soundcard.getUsb (); if (usb! = null) {version = usb.getVersion (); - อย่างไรก็ตามคุณจะเห็นว่ารหัสข้างต้นมีการตรวจสอบโมฆะมากเกินไปและโครงสร้างรหัสทั้งหมดกลายเป็นเรื่องน่าเกลียดมาก แต่เราต้องใช้การตัดสินนี้เพื่อให้แน่ใจว่าจะไม่มีพอยน์เตอร์ว่างเมื่อระบบทำงานอยู่ มันเป็นเรื่องน่ารำคาญที่จะตัดสินว่ามีการอ้างอิงที่ว่างเปล่ามากมายในรหัสธุรกิจของเราและมันยังนำไปสู่การอ่านรหัสของเราไม่ดี
หากคุณลืมที่จะตรวจสอบว่าค่านั้นว่างเปล่าหรือไม่การอ้างอิงที่เป็นโมฆะก็มีปัญหาที่อาจเกิดขึ้นได้เช่นกัน ในบทความนี้ฉันจะพิสูจน์ว่าการใช้การอ้างอิง null เป็นตัวแทนที่ไม่มีค่าเป็นวิธีที่ไม่ดี เราต้องการแบบจำลองที่ดีกว่าที่ระบุว่าไม่มีค่าแทนที่จะใช้การอ้างอิงโมฆะอีกครั้ง
Java 8 แนะนำคลาสใหม่ที่เรียกว่า java.util.Optional<T> ซึ่งได้รับแรงบันดาลใจจากภาษา Haskell และภาษา Scala คลาสนี้สามารถมีค่าโดยพลการดังแสดงในรูปและรหัสด้านล่าง คุณสามารถคิดว่าเป็นตัวเลือกเป็นค่าที่อาจมีค่า หากตัวเลือกไม่มีค่านั้นจะว่างเปล่าดังแสดงในรูปด้านล่าง
คอมพิวเตอร์คลาสสาธารณะ {ตัวเลือกส่วนตัว <SoundCard> SoundCard; ตัวเลือกสาธารณะ <SoundCard> getSoundCard () {... } ... } soundcard คลาสสาธารณะ {ตัวเลือกส่วนตัว <Sb> USB; ตัวเลือกสาธารณะ <SB> getUSB () {... }} คลาสสาธารณะ USB {Public String GetVersion () {... }} รหัสด้านบนแสดงให้เห็นว่าคอมพิวเตอร์อาจแทนที่การ์ดเสียง (การ์ดเสียงอาจมีหรือไม่มีอยู่) การ์ดเสียงอาจรวมถึงพอร์ต USB นี่เป็นวิธีการปรับปรุงและแบบจำลองสามารถสะท้อนได้อย่างชัดเจนยิ่งขึ้นว่าอาจไม่มีค่าที่กำหนด
แต่จะจัดการกับวัตถุ Optional<Soundcard> ได้อย่างไร? ท้ายที่สุดสิ่งที่คุณต้องการได้รับคือหมายเลขพอร์ต USB มันง่ายมาก คลาสเพิ่มเติมมีวิธีการบางอย่างในการจัดการว่ามีค่าอยู่หรือไม่ เมื่อเปรียบเทียบกับการอ้างอิงแบบ null คลาสที่เป็นตัวเลือกบังคับให้คุณจัดการว่าค่านั้นเกี่ยวข้องหรือไม่ดังนั้นจึงหลีกเลี่ยงข้อยกเว้นตัวชี้โมฆะ
ควรสังเกตว่าคลาสเพิ่มเติมไม่ได้แทนที่การอ้างอิง NULL ในทางตรงกันข้ามเพื่อให้ API ออกแบบง่ายขึ้นเมื่อคุณเห็นลายเซ็นของฟังก์ชั่นคุณสามารถตรวจสอบได้ว่าค่าที่จะส่งไปยังฟังก์ชั่นอาจไม่มีอยู่หรือไม่ สิ่งนี้จะแจ้งให้คุณเปิดคลาสเสริมเพื่อจัดการกับค่าจริง
ใช้โหมดเสริม
หลังจากพูดมากมาดูรหัสกันเถอะ! ก่อนอื่นมาดูวิธีการใช้ตัวเลือกเพื่อเขียนการตรวจจับอ้างอิงโมฆะแบบดั้งเดิมใหม่ ในตอนท้ายของบทความนี้คุณจะเข้าใจวิธีใช้ทางเลือก
ชื่อสตริง = คอมพิวเตอร์ flatmap (คอมพิวเตอร์ :: getSoundCard) .flatmap (SoundCard :: getUSB) .MAP (USB :: getVersion) .orelse ("ไม่ทราบ"); สร้างวัตถุเสริม
สามารถสร้างวัตถุทางเลือกที่ว่างเปล่าได้:
ตัวเลือก <soundcard> sc = potlienal.empty ();
ถัดไปคือการสร้างค่าที่ไม่ได้เป็นตัวเลือก:
SoundCard SoundCard = new SoundCard (); ตัวเลือก <soundcard> sc = ploriveal.of (SoundCard);
หากการ์ดเสียงเป็นโมฆะข้อยกเว้นตัวชี้ว่างจะถูกโยนทันที (นี่จะดีกว่าแค่โยนมันเมื่อคุณได้รับแอตทริบิวต์การ์ดเสียง)
โดยใช้ Oflulable คุณสามารถสร้างวัตถุเสริมที่อาจมีการอ้างอิงที่เป็นโมฆะ:
ตัวเลือก <soundcard> sc = เป็นตัวเลือก. ofnullable (soundcard);
หากการ์ดเสียงเป็นข้อมูลอ้างอิงที่เป็นโมฆะวัตถุเสริมจะว่างเปล่า
การประมวลผลค่าเป็นทางเลือก
ตอนนี้มีวัตถุเสริมคุณสามารถเรียกวิธีการที่เกี่ยวข้องเพื่อจัดการว่าค่าในวัตถุเสริมมีอยู่หรือไม่ เมื่อเทียบกับการตรวจจับ null เราสามารถใช้วิธี ifpresent () เช่นนี้:
ตัวเลือก <SoundCard> SoundCard = ... ; SoundCard.ifpresent (System.out :: Println);
ด้วยวิธีนี้ไม่จำเป็นต้องตรวจจับโมฆะ หากวัตถุเสริมว่างเปล่าข้อมูลใด ๆ จะไม่ถูกพิมพ์
นอกจากนี้คุณยังสามารถใช้วิธี isPresent() เพื่อดูว่าวัตถุเสริมมีอยู่จริงหรือไม่ นอกจากนี้ยังมีวิธีการรับ () ที่ส่งคืนค่าที่รวมอยู่ในวัตถุเสริมหากมีอยู่ มิฉะนั้นจะมีการโยน nosuchelementexception วิธีการทั้งสองนี้สามารถใช้ร่วมกันได้เช่นนี้เพื่อหลีกเลี่ยงข้อยกเว้น:
if (soundcard.ispresent ()) {system.out.println (soundcard.get ());} อย่างไรก็ตามวิธีนี้ไม่แนะนำให้ใช้ (ไม่มีการปรับปรุงเมื่อเทียบกับการตรวจจับโมฆะ) ด้านล่างเราจะหารือเกี่ยวกับวิธีการทำงานตามปกติ
ส่งคืนค่าเริ่มต้นและการดำเนินการที่เกี่ยวข้อง
เมื่อพบ null การดำเนินการปกติคือการส่งคืนค่าเริ่มต้นซึ่งคุณสามารถใช้นิพจน์ ternary เพื่อนำไปใช้:
SoundCard SoundCard = MaybesoundCard! = null? MaybesoundCard: SoundCard ใหม่ ("MASIC_SOUND_CARD"); หากคุณใช้วัตถุเสริมคุณสามารถใช้ orElse() เพื่อแทนที่ เมื่อตัวเลือกเป็น orElse() สามารถส่งคืนค่าเริ่มต้น:
SoundCard SoundCard = MaybesoundCard.orelse (SoundCard ใหม่ ("defaut")); ในทำนองเดียวกันเมื่อตัวเลือกว่างเปล่า orelsethrow () สามารถใช้เพื่อโยนข้อยกเว้น:
SoundCard SoundCard = MaybesoundCard.orelsethrow (ผิดกฎหมาย StateException :: ใหม่);
ใช้ตัวกรองเพื่อกรองค่าเฉพาะ
เรามักจะเรียกวิธีการวัตถุเพื่อตัดสินคุณสมบัติของมัน ตัวอย่างเช่นคุณอาจต้องตรวจสอบว่าหมายเลขพอร์ต USB เป็นค่าเฉพาะหรือไม่ ด้วยเหตุผลด้านความปลอดภัยคุณต้องตรวจสอบว่าการใช้งานทางการแพทย์ที่ชี้ไปที่ USB นั้นเป็นโมฆะหรือไม่จากนั้นเรียกใช้วิธี getVersion() เช่นนี้:
usb usb = ... ; ถ้า (usb! = null && "3.0" .equals (usb.getVersion ())) {system.out.println ("ตกลง");} หากคุณใช้ตัวเลือกคุณสามารถใช้ฟังก์ชั่นตัวกรองเพื่อเขียนใหม่:
เป็นทางเลือก <usb> อาจเป็นไปได้ = ... ; อาจจะเป็น filter (usb -> "3.0" .equals (usb.getVersion ()) .ifpresent (() -> system.out.println ("ตกลง")); วิธีการกรองต้องใช้เพรดิเคตตรงข้ามเป็นพารามิเตอร์ หากค่าในทางเลือกมีอยู่และตอบสนองการคาดการณ์ฟังก์ชั่นตัวกรองจะส่งคืนค่าที่ตรงตามเงื่อนไข; มิฉะนั้นวัตถุทางเลือกที่ว่างเปล่าจะถูกส่งคืน
ใช้วิธีการแผนที่เพื่อแยกและแปลงข้อมูล
รูปแบบทั่วไปคือการแยกคุณสมบัติบางอย่างของวัตถุ ตัวอย่างเช่นสำหรับวัตถุ SoundCard คุณอาจต้องได้รับวัตถุ USB จากนั้นกำหนดหมายเลขเวอร์ชัน โดยปกติแล้วการใช้งานของเราจะเป็นเช่นนี้:
if (soundcard! = null) {usb usb = soundcard.getUsb (); if (usb! = null && "3.0" .equals (usb.getVersion ()) {system.out.println ("ตกลง");}} เราสามารถใช้วิธีการแผนที่เพื่อแทนที่การตรวจจับนี้เป็นโมฆะจากนั้นแยกวัตถุของประเภทวัตถุ
ตัวเลือก <sb> usb = maybesoundcard.map (soundcard :: getusb);
นี่เป็นเช่นเดียวกับการใช้ฟังก์ชันแผนที่โดยใช้สตรีม การใช้สตรีมต้องผ่านฟังก์ชั่นเป็นพารามิเตอร์ไปยังฟังก์ชันแผนที่และฟังก์ชันที่ผ่านจะถูกนำไปใช้กับแต่ละองค์ประกอบในสตรีม เมื่อสตรีมพื้นที่และเวลาไม่มีอะไรเกิดขึ้น
ค่าที่อยู่ในตัวเลือกจะถูกแปลงโดยฟังก์ชั่นที่ส่งผ่าน (นี่คือฟังก์ชั่นที่ได้รับ USB จากการ์ดเสียง) หากวัตถุเสริมเป็นเวลาว่างจะไม่มีอะไรเกิดขึ้น
จากนั้นเรารวมวิธีการแผนที่และวิธีการกรองเพื่อกรองการ์ดเสียงด้วยหมายเลขเวอร์ชัน USB ไม่ใช่ 3.0
maybesoundcard.map (SoundCard :: getUsb) .filter (USB -> "3.0" .Equals (usb.getVersion ()) .ifpresent (() -> system.out.println ("ตกลง")); ด้วยวิธีนี้รหัสของเราเริ่มมีลักษณะเหมือนสิ่งที่เราให้ในตอนแรกโดยไม่ต้องตรวจจับเป็นโมฆะ
ผ่านวัตถุเสริมโดยใช้ฟังก์ชั่น flatmap
ตอนนี้มีการแนะนำตัวอย่างของวิธีการ refactor รหัสโดยใช้ตัวเลือกได้รับการแนะนำ ดังนั้นเราควรใช้รหัสต่อไปนี้อย่างปลอดภัยอย่างไร
สตริงเวอร์ชัน = คอมพิวเตอร์ getSoundCard (). getUSB (). getVersion ();
โปรดทราบว่ารหัสด้านบนทั้งหมดเป็นการแยกวัตถุอื่นจากวัตถุหนึ่งซึ่งสามารถนำไปใช้งานได้โดยใช้ฟังก์ชันแผนที่ ในบทความก่อนหน้านี้เราตั้งค่าวัตถุ Optional<Soundcard> ในคอมพิวเตอร์และการ์ด SoundCard มีวัตถุ Optional<USB> ดังนั้นเราจึงสามารถปรับรหัสด้วยวิธีนี้
สตริงเวอร์ชัน = Computer.map (คอมพิวเตอร์ :: getSoundCard) .map (SoundCard :: getUSB) .MAP (USB :: GetVersion) .orelse ("ไม่ทราบ"); น่าเสียดายที่รหัสด้านบนรวบรวมข้อผิดพลาดดังนั้นทำไม? ตัวแปรคอมพิวเตอร์เป็นตัว Optional<Computer> ดังนั้นจึงไม่มีปัญหาในการเรียกใช้ฟังก์ชันแผนที่ อย่างไรก็ตามเมธอด getSoundcard() ส่งคืนวัตถุ Optional<Soundcard> ซึ่งส่งคืนวัตถุประเภท Optional<Optional<Soundcard>> หลังจากเรียกฟังก์ชันแผนที่ที่สองแล้วการเรียกใช้ฟังก์ชัน getUSB() จะผิดกฎหมาย
รูปต่อไปนี้อธิบายสถานการณ์นี้:
การใช้ซอร์สโค้ดการใช้งานฟังก์ชันแผนที่มีดังนี้:
สาธารณะ <u> ตัวเลือก <u> แผนที่ (ฟังก์ชั่น <? super t,? ขยาย u> mapper) {objects.requirenonnull (mapper); if (! ispresent ()) return empty (); else {return potlional.ofnullable (mapper.apply (value)); - จะเห็นได้ว่าฟังก์ชั่นแผนที่จะเรียกว่า Optional.ofNullable() อีกครั้งส่งผลให้กลับมาของ Optional<Optional<Soundcard>>
ตัวเลือกให้ฟังก์ชั่น FlatMap ซึ่งออกแบบมาเพื่อแปลงค่าของวัตถุเสริม (เช่นการดำเนินการแผนที่) จากนั้นบีบอัดสองระดับเป็นตัวเลือกเป็นหนึ่งเดียว รูปต่อไปนี้แสดงความแตกต่างระหว่างวัตถุเสริมในการแปลงประเภทโดยการเรียกแผนที่และ FlatMap:
ดังนั้นเราจึงสามารถเขียนสิ่งนี้:
สตริงเวอร์ชัน = คอมพิวเตอร์ flatmap (คอมพิวเตอร์ :: getSoundCard) .flatmap (SoundCard :: getUSB) .MAP (USB :: getVersion) .orelse ("ไม่ทราบ"); Flatmap แรกทำให้มั่นใจได้ว่าการส่งคืนเป็น Optional<Soundcard> แทนที่จะเป็น Optional<Optional<Soundcard>> และ Flatmap ที่สองใช้ฟังก์ชั่นเดียวกันเพื่อให้การส่งคืนเป็น Optional<USB> โปรดทราบว่า map() เรียกว่าครั้งที่สามเนื่องจาก getVersion() ส่งคืนวัตถุสตริงแทนวัตถุเสริม
ในที่สุดเราก็เขียนรหัสที่น่าเกลียดของการตรวจสอบ NULL ที่ซ้อนกันที่เราเพิ่งเริ่มใช้ซึ่งสามารถอ่านได้สูงและยังหลีกเลี่ยงการเกิดข้อยกเว้นตัวชี้โมฆะ
สรุป
ในบทความนี้เรานำคลาส java.util.Optional<T> มาใช้โดย Java 8 ความตั้งใจดั้งเดิมของคลาสนี้ไม่ได้แทนที่การอ้างอิงที่เป็นโมฆะ แต่เพื่อช่วยให้นักออกแบบออกแบบ API ที่ดีขึ้น เพียงอ่านลายเซ็นของฟังก์ชั่นและรู้ว่าฟังก์ชั่นยอมรับค่าที่อาจมีหรือไม่มีอยู่จริง นอกจากนี้ยังบังคับให้คุณเปิดตัวเลือกแล้วจัดการว่ามีค่าอยู่หรือไม่ซึ่งทำให้รหัสของคุณหลีกเลี่ยงข้อยกเว้นตัวชี้โมฆะที่อาจเกิดขึ้น
โอเคข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com