สรุป
เมื่อฉันว่างฉันจะใช้เวลาในการเรียนรู้ Groovy และ Scala ที่ทำงานบน JVM และพบว่าพวกเขาจัดการกับ Null อย่างระมัดระวังมากกว่า Java รุ่นก่อนหน้า ใน Java 8 ตัวเลือกให้โซลูชันที่สง่างามมากสำหรับการประมวลผลการเขียนโปรแกรมที่ใช้งานได้ บทความนี้จะอธิบายการจัดการที่ไม่ดีของ null ที่ไม่ดีใน Java จากนั้นแนะนำการใช้ตัวเลือกเพื่อใช้การเขียนโปรแกรมการทำงานของ Java
โมฆะที่รบกวนเราในช่วงหลายปีที่ผ่านมา
มีตำนานในโลก Java: เฉพาะเมื่อคุณเข้าใจข้อยกเว้นตัวชี้ว่างอย่างแท้จริงคุณจะได้รับการพิจารณาว่าเป็นนักพัฒนา Java ที่ผ่านการรับรอง ในอาชีพตัวละคร Java ของเราเราจะพบกับการประมวลผลโมฆะต่าง ๆ ทุกวัน เราอาจเขียนรหัสเช่นต่อไปนี้ซ้ำ ๆ ทุกวัน:
if (null! = obj1) {ถ้า (null! = obje2) {// ทำอะไร}}หากคุณมีวิสัยทัศน์เล็กน้อย Javaer จะทำสิ่งที่สวยงามและหาวิธีตัดสิน Null:
บูลีน checknotNull (Object obj) {return null == obj? เท็จ: จริง; } void do () {ถ้า (checknotNull (obj1)) {ถ้า (checknotNull (obj2)) {// ทำอะไร}}}}จากนั้นคำถามจะมาอีกครั้ง: ถ้าค่า null แสดงถึงสตริงที่ว่างเปล่า "" หมายถึงอะไร?
จากนั้นการคิดแบบเฉื่อยก็บอกเราว่า "" และ NULL เป็นทั้งรหัสสตริงที่ว่างเปล่า? ฉันแค่อัพเกรดค่า NULL:
Boolean CheckNotBlank (Object obj) {return null! = obj &&! "". เท่ากับ (obj)? จริง: เท็จ; } void do () {ถ้า (checknotblank (obj1)) {ถ้า (checknotNull (obj2)) {// ทำอะไร}}}หากคุณมีเวลาคุณสามารถตรวจสอบจำนวนรหัสที่คุณเขียนในโครงการปัจจุบันหรือรหัสที่ผ่านมาของคุณและสิ่งที่คล้ายกับรหัสข้างต้น
ฉันสงสัยว่าคุณคิดอย่างจริงจังเกี่ยวกับคำถาม: โมฆะหมายถึงอะไร?
การระลึกถึงเราพบ java.lang.nullpointerexception กี่ครั้งในอาชีพการแกรมมิ่งก่อนหน้าของเรา? NullPoInterException เป็นข้อยกเว้นระดับ RuntimeException ที่ไม่จำเป็นต้องจับ หากเราจัดการกับมันโดยไม่ตั้งใจเรามักจะเห็นเอาต์พุตสแต็กข้อยกเว้นต่าง ๆ ที่เกิดจาก nullpointerexception ในบันทึกการผลิต และจากข้อมูลสแต็กข้อยกเว้นนี้เราไม่สามารถค้นหาสาเหตุของปัญหาได้เลยเพราะมันไม่ใช่สถานที่ที่ Nullpointerexception ถูกโยนทิ้งซึ่งทำให้เกิดปัญหา เราต้องลึกลงไปเพื่อค้นหาว่ามีการสร้างโมฆะนี้ที่ไหนและบันทึกมักจะไม่สามารถติดตามได้ในเวลานี้
บางครั้งมันก็น่าเศร้ายิ่งกว่าที่สถานที่ที่มีการผลิตค่า NULL มักจะไม่ได้อยู่ในรหัสโครงการของเราเอง สิ่งนี้มีข้อเท็จจริงที่น่าอายมากขึ้น - เมื่อเราเรียกอินเทอร์เฟซของบุคคลที่สามที่มีคุณภาพแตกต่างกันมันยากที่จะบอกว่าอินเทอร์เฟซส่งคืนค่าว่างโดยบังเอิญ ...
กลับไปที่ปัญหาความรู้ความเข้าใจก่อนหน้านี้ของ NULL ชาวชวาหลายคนเชื่อว่า null หมายถึง "ไม่มีอะไร" หรือ "คุณค่าไม่มีอยู่" ตามการคิดแบบเฉื่อยนี้ตรรกะรหัสของเราคือ: คุณเรียกอินเทอร์เฟซของฉันและส่งคืน "ค่า" ที่เกี่ยวข้องตามพารามิเตอร์ที่คุณให้ฉัน หากเงื่อนไขนี้ไม่สามารถหา "ค่า" ที่สอดคล้องกันได้แน่นอนฉันจะส่งคืนค่าว่างให้คุณว่าไม่มี "สิ่ง" มาดูรหัสต่อไปนี้ซึ่งเขียนในรูปแบบการเข้ารหัสแบบดั้งเดิมและมาตรฐาน Java:
คลาส MyEntity {int id; ชื่อสตริง; สตริง getName () {ชื่อคืน; }} // การทดสอบคลาส MainPublic {โมฆะสาธารณะคงที่หลัก (สตริง [] args) สุดท้าย myEntity myEntity = getMyEntity (เท็จ); System.out.println (myEntity.getName ()); } ส่วนตัว getMyEntity (Boolean ISSUC) {ถ้า (ISSUC) {ส่งคืน myEntity ใหม่ (); } else {return null; -รหัสชิ้นนี้ง่ายมากและรหัสธุรกิจประจำวันนั้นซับซ้อนกว่านี้มาก แต่อันที่จริงแล้วรหัส Java ของเราจำนวนมากถูกเขียนขึ้นตามกิจวัตรนี้ คนที่รู้วิธีใช้สินค้าสามารถมองเห็นได้อย่างรวดเร็วว่าพวกเขาจะโยน nullpointerexception ในที่สุด อย่างไรก็ตามเมื่อเราเขียนรหัสธุรกิจเราไม่ค่อยนึกถึงการจัดการกับ null ที่เป็นไปได้นี้ (บางทีเอกสาร API ได้รับการเขียนอย่างชัดเจนมากและจะคืนค่า null ในบางกรณี แต่คุณแน่ใจว่าคุณจะอ่านเอกสาร API อย่างระมัดระวังก่อนที่จะเริ่มเขียนรหัส?
// การทดสอบคลาส MainPublic {โมฆะสาธารณะคงที่หลัก (สตริง [] args) สุดท้าย myEntity myEntity = getMyEntity (เท็จ); if (null! = myEntity) {system.out.println (myEntity.getName ()); } else {system.out.println ("ข้อผิดพลาด"); -คิดอย่างรอบคอบเกี่ยวกับช่วงไม่กี่ปีที่ผ่านมาพวกเราทำสิ่งนี้หรือไม่? หากปัญหาว่างบางอย่างไม่สามารถค้นพบได้จนกว่าจะถึงขั้นตอนการทดสอบปัญหาก็คือตอนนี้ - มีจำนวนค่า null ที่ไม่ถูกต้องในรหัสธุรกิจที่ซับซ้อนและมีโครงสร้างที่ดี
วุฒิภาวะและความเข้มงวดของโครงการมักจะเห็นได้เมื่อต้องรับมือกับ NULL ตัวอย่างเช่น Guava ให้วิธีการประมวลผลโมฆะที่สง่างามก่อน JDK1.6 ซึ่งแสดงให้เห็นว่าทักษะนั้นลึกเพียงใด
Nulls Ghostly ป้องกันเราจากความคืบหน้า
หากคุณเป็น Javaer ที่มุ่งเน้นไปที่การพัฒนาเชิงวัตถุแบบดั้งเดิมคุณอาจคุ้นเคยกับปัญหาต่าง ๆ ที่เกิดจาก Null แต่หลายปีที่ผ่านมาพระเจ้าผู้ยิ่งใหญ่กล่าวว่า Null เป็นหลุม
โทนี่ฮอลล์ (คุณไม่รู้หรือไม่ว่าผู้ชายคนนี้คือใครไปตรวจสอบด้วยตัวเอง) ครั้งหนึ่งเคยพูดว่า: "ฉันเรียกมันว่าความผิดพลาดพันล้านดอลลาร์ของฉันมันเป็นสิ่งประดิษฐ์ของการอ้างอิงโมฆะในปี 1965 ฉันไม่สามารถต้านทานสิ่งล่อใจที่จะอ้างอิงโมฆะเพียงเพราะมันง่ายมากที่จะนำไปใช้" (ความหมายทั่วไปคือ: "ฉันเรียกมันว่าการประดิษฐ์ของ null เป็นความผิดพลาดอันล้ำค่าเพราะในถิ่นทุรกันดารของคอมพิวเตอร์ในปี 1965 การอ้างอิงที่ว่างเปล่านั้นง่ายเกินไปที่จะนำไปใช้ดังนั้นฉันจึงไม่สามารถต้านทานสิ่งล่อใจที่จะคิดค้นตัวชี้โมฆะ")
จากนั้นมาดูกันว่าปัญหาอื่น ๆ จะแนะนำอะไร
ตรวจสอบรหัสต่อไปนี้:
String address = person.getCountry (). getProvince (). getCity ();
หากคุณเล่นภาษาที่ใช้งานได้ (Haskell, Erlang, Clojure, Scala ฯลฯ ) ข้างต้นเป็นวิธีการเขียนที่เป็นธรรมชาติมาก แน่นอนวิธีการเขียนข้างต้นสามารถนำไปใช้โดยใช้ Java
แต่เพื่อที่จะจัดการข้อยกเว้นที่เป็นไปได้ทั้งหมดอย่างสมบูรณ์แบบเราต้องเปลี่ยนกระบวนทัศน์การเขียนโปรแกรมฟังก์ชั่นที่สง่างามนี้เป็นสิ่งนี้:
if (person! = null) {Country Country = person.getCountry (); ถ้า (ประเทศ! = null) {จังหวัด = ประเทศ. getProvince (); if (จังหวัด! = null) {address = province.getCity (); -ในทันทีการเขียนโปรแกรมการทำงานที่มีคุณภาพสูง Java8 กลับมาถึง 10 ปีที่ผ่านมา การตัดสินที่ซ้อนกันดังกล่าวยังคงเป็นเรื่องเล็กน้อยเพิ่มจำนวนรหัสและการไม่ลงรอยกัน มีแนวโน้มที่จะเกิดขึ้น: ส่วนใหญ่แล้วผู้คนจะลืมตัดสินโมฆะที่อาจเกิดขึ้นแม้แต่ผู้สูงอายุที่เขียนรหัสมาหลายปีก็ไม่มีข้อยกเว้น
ส่วนข้างต้นของการประมวลผล null ซึ่งเป็นเลเยอร์ซ้อนกันโดยเลเยอร์ก็เป็นส่วนหนึ่งของชวาดั้งเดิมที่วิพากษ์วิจารณ์เป็นเวลานาน หากคุณใช้เวอร์ชัน Java ต้นเป็นภาษาการตรัสรู้ของคุณกลิ่นของ get-> ถ้า null-> return จะส่งผลกระทบต่อคุณเป็นเวลานาน (จำไว้ในชุมชนต่างประเทศซึ่งเรียกว่า: การพัฒนาที่มุ่งเน้นเอนทิตี)
การใช้ตัวเลือกเพื่อใช้การเขียนโปรแกรมการทำงานของ Java
โอเคหลังจากพูดถึงปัญหาทุกประเภทเราสามารถเข้าสู่ยุคใหม่ได้
นานก่อนที่จะเปิดตัว Java SE 8 เวอร์ชันภาษาการพัฒนาที่ใช้งานได้อื่น ๆ ที่คล้ายกันมีวิธีแก้ปัญหาต่าง ๆ ของตัวเอง นี่คือรหัสของ Groovy:
สตริงเวอร์ชัน = คอมพิวเตอร์? .getSoundCard () ?. getUSB ()?
Haskell ใช้ตัวระบุคลาสประเภทอาจจะประมวลผลค่าโมฆะ Scala หรือที่รู้จักกันในชื่อภาษาการพัฒนาหลาย paradigm ให้ตัวเลือก [t] ที่คล้ายกับบางทีสำหรับการห่อและการประมวลผล null
java8 แนะนำ java.util.optional <t> เพื่อจัดการกับปัญหาโมฆะของการเขียนโปรแกรมที่ใช้งานได้ แนวคิดการประมวลผลของตัวเลือก <t> นั้นคล้ายกับของ Haskell และ Scala แต่มีความแตกต่างบางอย่าง ลองดูตัวอย่างของรหัส Java ต่อไปนี้:
การทดสอบระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ข้อความสตริงสุดท้าย = "Hallo World!"; ตัวเลือก. ofnullable (ข้อความ) // แสดงเพื่อสร้าง shell.map เสริม (ทดสอบ :: print) .map (ทดสอบ :: print) .ifpresent (System.out :: println); ตัวเลือก. ofnullable (text) .map (s -> {system.out.println (s); return s.substring (6);}) .map (s -> null) // return null .ifpresent (System.out :: println); } // พิมพ์และสกัดกั้นสตริงหลังจาก str [5] การพิมพ์สตริงแบบคงที่ส่วนตัว (สตริง str) {system.out.println (str); return str.substring (6); }} // consol output // num1: Hallo World! // num2: World! // num3: // num4: Hallo World!(คุณสามารถคัดลอกรหัสด้านบนลงใน IDE ของคุณได้หากต้องติดตั้ง JDK8)
ตัวเลือกสองตัวถูกสร้างขึ้นในรหัสข้างต้นและฟังก์ชั่นที่ใช้งานนั้นเหมือนกัน พวกเขาทั้งสองใช้ตัวเลือกเป็นเชลล์สตริงเพื่อตัดสาย เมื่อพบค่า NULL ในระหว่างการประมวลผลการประมวลผลจะไม่ดำเนินการต่ออีกต่อไป เราสามารถค้นหาได้ว่าหลังจาก s-> null ปรากฏในตัวเลือกที่สอง ifpresent ที่ตามมาจะไม่ถูกดำเนินการอีกต่อไป
ให้ความสนใจกับเอาต์พุต // num3:, ซึ่งหมายความว่าอักขระ "" เป็นเอาต์พุตไม่ใช่โมฆะ
ตัวเลือกให้อินเทอร์เฟซที่หลากหลายเพื่อจัดการกับสถานการณ์ต่าง ๆ เช่นการแก้ไขรหัสเป็น:
การทดสอบระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ข้อความสตริงสุดท้าย = "Hallo World!"; System.out.println (ตัวพิมพ์เล็ก (ข้อความ)); // วิธีหนึ่งตัวพิมพ์เล็ก (null, system.out :: println); // วิธีสอง} ตัวพิมพ์เล็กสตริงคงที่ส่วนตัว (สตริง str) {return ontaint.ofnullable (str) .map (s -> s.toLowercase () } ตัวพิมพ์ใหญ่แบบคงที่ส่วนตัว (สตริง Str, ผู้บริโภค <String> ผู้บริโภค) {consumer.accept (ตัวพิมพ์เล็ก (str)); }} // output // hallo java! // nanด้วยวิธีนี้เราสามารถประมวลผลสตริงได้แบบไดนามิก หากพบว่าค่าเป็นโมฆะตลอดเวลาเราใช้ Orelse เพื่อส่งคืนค่าเริ่มต้นที่ตั้งไว้ล่วงหน้า "Nan"
โดยทั่วไปเราสามารถห่อโครงสร้างข้อมูลใด ๆ ในทางเลือกแล้วประมวลผลในวิธีการใช้งานโดยไม่ต้องกังวลเกี่ยวกับ nulls ที่อาจปรากฏขึ้นได้ตลอดเวลา
ลองมาดูกันว่าบุคคล GetCountry (). getProvince (). getCity () ที่กล่าวถึงก่อนหน้านี้ไม่จำเป็นต้องใช้ IFS จำนวนมากในการจัดการ
วิธีแรกคือการไม่เปลี่ยนเอนทิตีก่อนหน้า:
นำเข้า java.util.optional; การทดสอบระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (String [] args) {system.out.println (potainal.ofnullable (บุคคลใหม่ ()) .map (x-> x.country) .map (x-> x.provinec) .map (x-> x.city) .orelse ("unkonwn")); }} คนในชั้นเรียน {ประเทศประเทศ;} ชนชั้นประเทศ {ProvisioneC CORMINTEC;} ระดับจังหวัด {City City;} Class City {ชื่อสตริง;}ที่นี่ตัวเลือกจะถูกใช้เป็นเชลล์ที่ส่งคืนทุกครั้ง หากตำแหน่งที่แน่นอนส่งคืนโมฆะคุณจะได้รับ "unkonwn"
วิธีที่สองคือการกำหนดค่าทั้งหมดในทางเลือก:
นำเข้า java.util.optional; การทดสอบระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {system.out.println (บุคคลใหม่ () .country.flatmap (x -> x.provinec) .flatmap }} คนในชั้นเรียน {ตัวเลือก <ประเทศ> country = poiceal.empty ();} ประเทศชั้นเรียน {ตัวเลือก <frovince> บทบัญญัติ c;} จังหวัดชั้นเรียน {ตัวเลือก <yity> เมือง; ตัวเลือก <yity> getCity () {// สำหรับ :: return City; }} คลาสเมือง {ตัวเลือก <String> ชื่อ;}วิธีแรกสามารถรวมเข้ากับ javabeans เอนทิตีหรือ poja ที่มีอยู่ได้อย่างราบรื่นโดยไม่มีการเปลี่ยนแปลงใด ๆ และสามารถรวมเข้ากับอินเทอร์เฟซของบุคคลที่สามได้ง่ายขึ้น (เช่นถั่วฤดูใบไม้ผลิ) ขอแนะนำให้ใช้วิธีการเลือกแรกเป็นวิธีหลัก ท้ายที่สุดไม่ใช่ทุกคนในทีมที่สามารถเข้าใจจุดประสงค์ของการรับ/ตั้งค่าแต่ละครั้งด้วยตัวเลือก
ตัวเลือกยังมีวิธีการกรองสำหรับการกรองข้อมูล (อันที่จริงอินเตอร์เฟสสไตล์สตรีมใน Java 8 มีวิธีการกรอง) ตัวอย่างเช่นในอดีตเราตัดสินว่าค่ามีอยู่และทำการประมวลผลที่สอดคล้องกัน:
if (จังหวัด! = null) {City City = Province.getCity (); if (null! = city && "guangzhou" .equals (city.getName ()) {system.out.println (city.getName ());} else {system.out.println ("unkonwn");}}}ตอนนี้เราสามารถปรับเปลี่ยนได้
ตัวเลือก. ofnullable (จังหวัด) .map (x-> x.city) .filter (x-> "guangzhou" .equals (x.getName ())) .map (x-> x.name) .orelse ("unkonw");ณ จุดนี้การแนะนำการเขียนโปรแกรมที่ใช้งานได้จะเสร็จสมบูรณ์โดยใช้ตัวเลือก นอกเหนือจากวิธีการที่กล่าวถึงข้างต้นตัวเลือกยังให้วิธีการตามความต้องการมากขึ้น Orelseget, Orelsethrow ฯลฯ Orelseget จะโยนข้อยกเว้นตัวชี้โมฆะเนื่องจากค่า Null และ Orelsethrow จะโยนข้อยกเว้นที่ผู้ใช้กำหนดเมื่อ Null เกิดขึ้น คุณสามารถดูเอกสาร API เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับวิธีการทั้งหมด
เขียนในตอนท้าย
ตัวเลือกเป็นเพียงส่วนเล็ก ๆ ของภูเขาน้ำแข็งของการเขียนโปรแกรมการทำงานของ Java มีความจำเป็นที่จะต้องรวมคุณสมบัติเช่นแลมบ์ดาสตรีมและฟังก์ชันการทำงานเพื่อทำความเข้าใจประสิทธิภาพของการเขียนโปรแกรมการทำงานของ Java8 อย่างแท้จริง เดิมทีฉันต้องการแนะนำซอร์สโค้ดและหลักการทำงานเสริมบางอย่าง แต่ตัวเลือกนั้นมีรหัสน้อยมากและไม่ได้มีอินเทอร์เฟซ API มากนัก หากคุณคิดอย่างระมัดระวังไม่มีอะไรจะพูดและคุณจะละเว้นมัน
แม้ว่าตัวเลือกจะมีความสง่างาม แต่โดยส่วนตัวแล้วฉันรู้สึกว่ามีปัญหาด้านประสิทธิภาพบางอย่าง แต่ยังไม่ได้รับการตรวจสอบ หากใครมีข้อมูลใด ๆ โปรดแจ้งให้เราทราบ
ฉันไม่ใช่ "ผู้สนับสนุนการเขียนโปรแกรมที่ใช้งานได้" จากมุมมองของผู้จัดการทีมสำหรับความยากลำบากในการเรียนรู้เล็กน้อยทุกครั้งจะเพิ่มขึ้นค่าใช้จ่ายในการใช้งานบุคลากรและการมีปฏิสัมพันธ์กับทีมจะสูงขึ้น เช่นเดียวกับในตำนาน LISP สามารถมีรหัสน้อยกว่า C ++ สามสิบเท่าและมีประสิทธิภาพในการพัฒนามากกว่า แต่ถ้า บริษัท ไอทีในประเทศใช้งาน LISP ทำโครงการได้ที่ไหนและมีค่าใช้จ่ายเท่าไหร่ที่จะได้รับคนเหล่านี้ที่ใช้ LISP
แต่ฉันขอแนะนำให้ทุกคนเรียนรู้และเข้าใจความคิดของการเขียนโปรแกรมที่ใช้งานได้ โดยเฉพาะอย่างยิ่งนักพัฒนาซอฟต์แวร์ที่เคยถูกโจมตีโดย Java และยังไม่ทราบว่าการเปลี่ยนแปลงใด ๆ Java8 จะนำมาซึ่ง Java8 เป็นโอกาสที่ดี นอกจากนี้ยังได้รับการสนับสนุนให้แนะนำคุณสมบัติ Java8 ใหม่ในโครงการปัจจุบัน ทีมที่ให้ความร่วมมือเป็นเวลานานและภาษาการเขียนโปรแกรมโบราณจำเป็นต้องฉีดพลังใหม่อย่างต่อเนื่องมิฉะนั้นคุณจะล่าถอยหากคุณไม่ก้าวหน้า
ข้างต้นคือการรวบรวมข้อมูลเพิ่มเติมของ Java เราจะยังคงเพิ่มข้อมูลที่เกี่ยวข้องในอนาคต ขอบคุณสำหรับการสนับสนุนเว็บไซต์นี้!