บทที่ 1 สวัสดี การแสดงออกของแลมบ์ดา!
ส่วนที่ 1
รูปแบบการเขียนโค้ดของ Java กำลังเผชิญกับการเปลี่ยนแปลงครั้งใหญ่
งานประจำวันของเราจะง่ายขึ้น สะดวกขึ้น และแสดงออกได้มากขึ้น Java ซึ่งเป็นวิธีการเขียนโปรแกรมแบบใหม่ได้ปรากฏในภาษาการเขียนโปรแกรมอื่นเมื่อหลายสิบปีก่อน หลังจากเปิดตัวคุณสมบัติใหม่เหล่านี้ใน Java แล้ว เราก็สามารถเขียนโค้ดที่กระชับ สวยงาม แสดงออกได้มากขึ้น และมีข้อผิดพลาดน้อยลง เราสามารถใช้กลยุทธ์และรูปแบบการออกแบบที่หลากหลายโดยใช้โค้ดน้อยลง
ในหนังสือเล่มนี้ เราจะสำรวจการเขียนโปรแกรมเชิงฟังก์ชันผ่านตัวอย่างจากการเขียนโปรแกรมในชีวิตประจำวัน ก่อนจะใช้วิธีการใหม่ที่สวยงามในการออกแบบและเขียนโค้ด ก่อนอื่นเรามาดูกันว่ามีข้อดีอะไรบ้างก่อน
เปลี่ยนวิธีคิดของคุณ
สไตล์ที่จำเป็น - นี่คือแนวทางที่ภาษา Java มีให้ตั้งแต่เริ่มต้น เมื่อใช้สไตล์นี้ เราต้องบอก Java ว่าต้องทำอะไรในแต่ละขั้นตอน จากนั้นดูมันดำเนินการจริงทีละขั้นตอน แน่นอนว่านี่เป็นสิ่งที่ดี แต่ดูเหมือนว่าจะเป็นพื้นฐานเล็กน้อย โค้ดดูละเอียดไปหน่อย และเราหวังว่าภาษาจะฉลาดขึ้นอีกหน่อย เราควรบอกมันว่าเราต้องการอะไร แทนที่จะบอกว่าต้องทำอย่างไร โชคดีที่ Java สามารถช่วยให้เราตระหนักถึงความปรารถนานี้ได้ในที่สุด ลองดูตัวอย่างบางส่วนเพื่อทำความเข้าใจข้อดีและความแตกต่างของสไตล์นี้
วิธีปกติ
เริ่มจากตัวอย่างที่คุ้นเคยสองตัวอย่าง นี่เป็นวิธีคำสั่งในการตรวจสอบว่าชิคาโกอยู่ในกลุ่มเมืองที่ระบุหรือไม่ - โปรดจำไว้ว่ารหัสที่แสดงอยู่ในหนังสือเล่มนี้เป็นเพียงบางส่วนเท่านั้น
คัดลอกรหัสรหัสดังต่อไปนี้:
พบบูลีน = เท็จ;
สำหรับ (เมือง String : เมือง) {
ถ้า (เมืองเท่ากับ ("ชิคาโก")) {
พบ = จริง;
หยุดพัก;
-
-
System.out.println("พบชิคาโก?:" + พบ);
เวอร์ชันที่จำเป็นนี้ดูละเอียดและเป็นพื้นฐานเล็กน้อย โดยแบ่งออกเป็นส่วนการดำเนินการหลายส่วน ขั้นแรก เริ่มต้นแท็กบูลีนที่เรียกว่า found จากนั้นสำรวจแต่ละองค์ประกอบในคอลเลกชัน หากพบเมืองที่เรากำลังมองหา ให้ตั้งค่าแท็กนี้ จากนั้นกระโดดออกจากลูป และสุดท้ายก็พิมพ์ผลการค้นหาออกมา
วิธีที่ดีกว่า
หลังจากอ่านโค้ดนี้แล้ว โปรแกรมเมอร์ Java ที่ระมัดระวังจะคิดหาวิธีที่กระชับและชัดเจนยิ่งขึ้นอย่างรวดเร็ว เช่นนี้
คัดลอกรหัสรหัสดังต่อไปนี้:
System.out.println("พบชิคาโก?:" + cities.contains("ชิคาโก"));
นี่เป็นรูปแบบการเขียนที่จำเป็นเช่นกัน - วิธีการบรรจุนั้นทำเพื่อเราโดยตรง
การปรับปรุงที่เกิดขึ้นจริง
การเขียนโค้ดลักษณะนี้มีข้อดีหลายประการ:
1. ไม่ต้องยุ่งกับตัวแปรที่ไม่แน่นอนอีกต่อไป
2. สรุปการวนซ้ำไปที่ชั้นล่างสุด
3. รหัสนั้นง่ายกว่า
4. รหัสมีความชัดเจนและเน้นมากขึ้น
5. ใช้ทางอ้อมน้อยลงและบูรณาการโค้ดและความต้องการทางธุรกิจให้ใกล้ชิดยิ่งขึ้น
6. มีแนวโน้มที่จะเกิดข้อผิดพลาดน้อยลง
7. เข้าใจง่ายและบำรุงรักษา
ลองยกตัวอย่างที่ซับซ้อนมากขึ้น
ตัวอย่างนี้ง่ายเกินไป การสืบค้นที่จำเป็นว่ามีองค์ประกอบอยู่ในคอลเลกชันที่สามารถเห็นได้ทุกที่ใน Java หรือไม่ ตอนนี้สมมติว่าเราต้องการใช้การเขียนโปรแกรมที่จำเป็นเพื่อดำเนินการขั้นสูงบางอย่าง เช่น การแยกวิเคราะห์ไฟล์ การโต้ตอบกับฐานข้อมูล การเรียกใช้บริการ WEB การเขียนโปรแกรมพร้อมกัน ฯลฯ ตอนนี้เราสามารถใช้ Java เพื่อเขียนโค้ดที่กระชับ สวยงาม และปราศจากข้อผิดพลาด ไม่ใช่แค่ในสถานการณ์ง่ายๆ นี้เท่านั้น
ทางเก่า
ลองดูอีกตัวอย่างหนึ่ง เรากำหนดช่วงราคาและคำนวณราคารวมที่มีส่วนลดด้วยวิธีต่างๆ
คัดลอกรหัสรหัสดังต่อไปนี้:
ราคารายการสุดท้าย <BigDecimal> = Arrays.asList(
ใหม่ BigDecimal("10"), BigDecimal ใหม่ ("30"), BigDecimal ใหม่ ("17"),
ใหม่ BigDecimal("20"), BigDecimal ใหม่ ("15"), BigDecimal ใหม่ ("18"),
ใหม่ BigDecimal("45"), ใหม่ BigDecimal("12"));
สมมติว่ามีส่วนลด 10% หากเกิน 20 หยวน เรามาปรับใช้ด้วยวิธีปกติก่อน
คัดลอกรหัสรหัสดังต่อไปนี้:
BigDecimal TotalOfDiscountedPrices = BigDecimal.ZERO;
สำหรับ(ราคา BigDecimal : ราคา) {
ถ้า(price.compareTo(BigDecimal.valueOf(20)) > 0)
ราคารวมของส่วนลด =
TotalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9)));
-
System.out.println("ราคารวมที่ลดราคา: " + TotalOfDiscountedPrices);
รหัสนี้ควรจะคุ้นเคยมาก ขั้นแรกให้ใช้ตัวแปรเพื่อจัดเก็บราคารวม จากนั้นวนซ้ำราคาทั้งหมด ค้นหาราคาที่มากกว่า 20 หยวน คำนวณราคาที่ลดราคาแล้วบวกเข้ากับราคารวม ราคาหลังหักส่วนลด.
นี่คือผลลัพธ์ของโปรแกรม:
คัดลอกรหัสรหัสดังต่อไปนี้:
ราคาลดรวม : 67.5
ผลลัพธ์ถูกต้องสมบูรณ์ แต่โค้ดยุ่งเล็กน้อย ไม่ใช่ความผิดของเราที่เราสามารถเขียนได้ในแบบที่เรามีเท่านั้น อย่างไรก็ตาม รหัสดังกล่าวค่อนข้างจะพื้นฐานเล็กน้อย ไม่เพียงแต่จะทนทุกข์ทรมานจากอาการหวาดระแวงขั้นพื้นฐานเท่านั้น แต่ยังละเมิดหลักความรับผิดชอบเดียวอีกด้วย หากคุณทำงานจากที่บ้านและมีเด็กๆ ที่อยากเป็นนักเขียนโค้ด คุณต้องซ่อนโค้ดของคุณไว้ เผื่อพวกเขาเห็นและถอนหายใจด้วยความผิดหวัง แล้วพูดว่า “คุณทำอาชีพนี้ได้เหรอ?”
มีวิธีที่ดีกว่า
เราสามารถทำได้ดีกว่า - และดีกว่ามาก รหัสของเราเหมือนกับข้อกำหนดข้อกำหนดเล็กน้อย สิ่งนี้สามารถจำกัดช่องว่างระหว่างข้อกำหนดทางธุรกิจและรหัสที่นำไปใช้งาน ช่วยลดความเป็นไปได้ของการตีความข้อกำหนดที่ผิด
เราไม่ปล่อยให้ Java สร้างตัวแปรและกำหนดตัวแปรอย่างไม่มีที่สิ้นสุดอีกต่อไป เราจำเป็นต้องสื่อสารกับมันจากระดับนามธรรมที่สูงกว่า ดังเช่นโค้ดต่อไปนี้
คัดลอกรหัสรหัสดังต่อไปนี้:
BigDecimal สุดท้าย TotalOfDiscountedPrices =
ราคา.สตรีม()
.filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
.map(price -> price.multiply(BigDecimal.valueOf(0.9)))
.reduce(BigDecimal.ZERO, BigDecimal::เพิ่ม);
System.out.println("ราคารวมที่ลดราคา: " + TotalOfDiscountedPrices);
อ่านออกเสียง - กรองราคาที่มากกว่า 20 หยวนออก แปลงเป็นราคาลดแล้วบวกเพิ่ม รหัสนี้เหมือนกับกระบวนการที่เราใช้ในการอธิบายข้อกำหนดของเราทุกประการ ใน Java จะสะดวกมากในการพับโค้ดบรรทัดยาวๆ และจัดเรียงโค้ดตามบรรทัดตามระยะเวลาที่อยู่หน้าชื่อเมธอด เช่นเดียวกับด้านบน
โค้ดนั้นง่ายมาก แต่เราใช้สิ่งใหม่ๆ มากมายใน Java8 ขั้นแรก เราเรียกวิธีสตรีมของรายการราคา นี่เป็นการเปิดประตูสู่ตัววนซ้ำที่สะดวกสบายจำนวนนับไม่ถ้วน ซึ่งเราจะหารือในภายหลัง
เราใช้วิธีการพิเศษบางอย่าง เช่น ตัวกรองและแผนที่ แทนที่จะสำรวจผ่านรายการทั้งหมดโดยตรง วิธีการเหล่านี้ไม่เหมือนกับวิธีการใน JDK ที่เราใช้ก่อนหน้านี้ โดยยอมรับ function-lambda expression- ที่ไม่ระบุชื่อเป็นพารามิเตอร์ (เราจะหารือเรื่องนี้ในเชิงลึกในภายหลัง) เราเรียกเมธอดลด() เพื่อคำนวณผลรวมของราคาที่ส่งคืนโดยเมธอด map()
เช่นเดียวกับวิธีการบรรจุ เนื้อความของลูปจะถูกซ่อนไว้ อย่างไรก็ตาม วิธีการแมป (และวิธีการกรอง) นั้นซับซ้อนกว่ามาก โดยจะเรียกนิพจน์ lambda ที่ส่งผ่านเพื่อคำนวณราคาแต่ละราคาในรายการราคา และนำผลลัพธ์ไปไว้ในคอลเลกชันใหม่ ในที่สุดเราก็เรียกวิธีการลดในคอลเลกชันใหม่นี้เพื่อให้ได้ผลลัพธ์สุดท้าย
นี่คือผลลัพธ์ของโค้ดด้านบน:
คัดลอกรหัสรหัสดังต่อไปนี้:
ราคาลดรวม : 67.5
พื้นที่สำหรับการปรับปรุง
นี่เป็นการปรับปรุงที่สำคัญกว่าการใช้งานครั้งก่อน:
1. มีโครงสร้างดีแต่ไม่เกะกะ
2. ไม่มีการดำเนินงานระดับต่ำ
3. ง่ายต่อการปรับปรุงหรือแก้ไขตรรกะ
4. การวนซ้ำตามไลบรารีเมธอด
5. มีประสิทธิภาพ การประเมินความขี้เกียจของตัวลูป
6. ขนานกันอย่างง่ายดาย
ด้านล่างนี้เราจะพูดถึงวิธีที่ Java นำสิ่งนี้ไปใช้
สำนวน Lambda มีไว้เพื่อช่วยโลก
นิพจน์ Lambda เป็นทางลัดที่ช่วยเราจากปัญหาในการเขียนโปรแกรมที่จำเป็น คุณสมบัติใหม่นี้จาก Java ได้เปลี่ยนวิธีการเขียนโปรแกรมดั้งเดิมของเรา ทำให้โค้ดที่เราเขียนไม่เพียงกระชับและสวยงาม ผิดพลาดน้อยลง แต่ยังมีประสิทธิภาพมากขึ้น ง่ายต่อการปรับแต่ง ปรับปรุง และขนานกัน
ส่วนที่ 2: ประโยชน์สูงสุดจากการเขียนโปรแกรมเชิงฟังก์ชัน
โค้ดสไตล์การทำงานมีอัตราส่วนสัญญาณต่อสัญญาณรบกวนที่สูงกว่า เขียนโค้ดน้อยลง แต่ทำได้มากกว่าต่อบรรทัดหรือนิพจน์ เมื่อเปรียบเทียบกับการเขียนโปรแกรมเชิงความจำเป็นแล้ว การเขียนโปรแกรมเชิงฟังก์ชันมีประโยชน์กับเราอย่างมาก:
หลีกเลี่ยงการแก้ไขหรือกำหนดตัวแปรอย่างชัดเจน ซึ่งมักเป็นสาเหตุของจุดบกพร่อง และทำให้โค้ดขนานกันได้ยาก ในการเขียนโปรแกรมบรรทัดคำสั่ง เราจะกำหนดค่าให้กับตัวแปร TotalOfDiscountedPrices ในเนื้อหาของลูปอย่างต่อเนื่อง ในรูปแบบการทำงาน โค้ดจะไม่ผ่านการดำเนินการแก้ไขที่ชัดเจนอีกต่อไป ยิ่งมีการแก้ไขตัวแปรน้อยลง รหัสก็จะยิ่งมีข้อบกพร่องน้อยลง
โค้ดรูปแบบการทำงานสามารถขนานกันได้อย่างง่ายดาย หากการคำนวณใช้เวลานาน เราสามารถรันองค์ประกอบรายการพร้อมกันได้อย่างง่ายดาย หากเราต้องการทำให้โค้ดที่จำเป็นขนานกัน เราต้องกังวลเกี่ยวกับปัญหาที่เกิดจากการแก้ไขตัวแปร TotalOfDiscountedPrices พร้อมกัน ในการเขียนโปรแกรมเชิงฟังก์ชัน เราจะเข้าถึงตัวแปรนี้หลังจากประมวลผลเสร็จสมบูรณ์แล้วเท่านั้น จึงขจัดข้อกังวลด้านความปลอดภัยของเธรด
รหัสมีความหมายมากขึ้น การเขียนโปรแกรมเชิงความจำเป็นแบ่งออกเป็นหลายขั้นตอนเพื่ออธิบายสิ่งที่ต้องทำ - สร้างค่าเริ่มต้น วนซ้ำราคา เพิ่มราคาส่วนลดให้กับตัวแปร ฯลฯ - ในขณะที่การเขียนโปรแกรมเชิงฟังก์ชันจำเป็นต้องมีวิธี map ของรายการเท่านั้นที่ส่งคืนค่ารวมทั้งส่วนลดด้วย เพียงสร้างรายการราคาใหม่แล้วสะสมไว้
การเขียนโปรแกรมเชิงฟังก์ชันนั้นง่ายกว่า ต้องใช้โค้ดน้อยกว่าเพื่อให้ได้ผลลัพธ์เดียวกันมากกว่าการเขียนโปรแกรมที่จำเป็น โค้ดที่สะอาดกว่าหมายถึงโค้ดที่ต้องเขียนน้อยลง อ่านน้อยลง และต้องดูแลรักษาน้อยลง ดูที่ "กระชับน้อยกว่าพอที่จะกระชับหรือไม่" ในหน้า 7
โค้ดการทำงานนั้นใช้งานง่ายกว่า การอ่านโค้ดก็เหมือนกับการอธิบายปัญหา และง่ายต่อการเข้าใจเมื่อเราคุ้นเคยกับไวยากรณ์แล้ว เมธอด map ดำเนินการฟังก์ชันที่กำหนด (คำนวณราคาส่วนลด) สำหรับแต่ละองค์ประกอบของคอลเลกชัน จากนั้นส่งคืนชุดผลลัพธ์ ดังแสดงในรูปด้านล่าง
รูปที่ 1 - แผนที่ทำหน้าที่ที่กำหนดในแต่ละองค์ประกอบในคอลเลกชัน
ด้วยนิพจน์แลมบ์ดา เราสามารถแสดงพลังของการเขียนโปรแกรมเชิงฟังก์ชันใน Java ได้อย่างเต็มที่ การใช้รูปแบบการทำงานทำให้คุณสามารถเขียนโค้ดได้ชัดเจน กระชับ มีการกำหนดน้อยลง และมีข้อผิดพลาดน้อยลง
การสนับสนุนการเขียนโปรแกรมเชิงวัตถุถือเป็นข้อได้เปรียบที่สำคัญของ Java การเขียนโปรแกรมเชิงฟังก์ชั่นและการเขียนโปรแกรมเชิงวัตถุไม่แยกจากกัน การเปลี่ยนแปลงรูปแบบที่แท้จริงคือจากการเขียนโปรแกรมบรรทัดคำสั่งไปจนถึงการเขียนโปรแกรมแบบประกาศ ใน Java 8 สามารถบูรณาการการทำงานและเชิงวัตถุได้อย่างมีประสิทธิภาพ เราสามารถใช้รูปแบบ OOP ต่อไปเพื่อสร้างแบบจำลองเอนทิตีโดเมน รวมถึงสถานะและความสัมพันธ์ได้ นอกจากนี้เรายังสามารถใช้ฟังก์ชันต่างๆ เพื่อสร้างแบบจำลองพฤติกรรมหรือการเปลี่ยนสถานะ เวิร์กโฟลว์และการประมวลผลข้อมูล และสร้างฟังก์ชันแบบผสมได้
ส่วนที่ 3: ทำไมต้องใช้สไตล์การใช้งาน?
เราได้เห็นข้อดีของ Functional Programming แล้ว แต่คุ้มค่าไหมที่จะใช้รูปแบบใหม่นี้ นี่เป็นเพียงการปรับปรุงเล็กน้อยหรือการเปลี่ยนแปลงทั้งหมดหรือไม่ ยังมีคำถามเชิงปฏิบัติอีกมากมายที่ต้องตอบก่อนที่เราจะใช้เวลากับเรื่องนี้จริงๆ
คัดลอกรหัสรหัสดังต่อไปนี้:
เสี่ยวหมิงถามว่า:
รหัสที่น้อยลงหมายถึงความเรียบง่ายหรือไม่?
ความเรียบง่ายหมายถึงน้อยแต่ไม่เกะกะ ในการวิเคราะห์ขั้นสุดท้ายหมายถึงสามารถแสดงเจตนาได้อย่างมีประสิทธิภาพ ผลประโยชน์มีมากมาย
การเขียนโค้ดก็เหมือนกับการซ้อนส่วนผสมเข้าด้วยกัน ความเรียบง่ายหมายถึงสามารถผสมส่วนผสมลงในเครื่องปรุงรสได้ การเขียนโค้ดที่กระชับต้องใช้ความพยายามอย่างหนัก มีโค้ดให้อ่านน้อยกว่า และโค้ดที่มีประโยชน์อย่างแท้จริงก็โปร่งใสสำหรับคุณ รหัสย่อที่เข้าใจยากหรือซ่อนรายละเอียดไว้จะสั้นแทนที่จะกระชับ
โค้ดง่ายๆ จริงๆ แล้วหมายถึงการออกแบบที่คล่องตัว รหัสง่าย ๆ ที่ไม่มีเทปสีแดง ซึ่งหมายความว่าเราสามารถลองใช้แนวคิดต่างๆ ได้อย่างรวดเร็ว ดำเนินการต่อหากทำงานได้ดี และข้ามไปได้อย่างรวดเร็วหากแนวคิดเหล่านั้นทำงานได้ไม่ดี
การเขียนโค้ดใน Java ไม่ใช่เรื่องยากและมีไวยากรณ์ที่เรียบง่าย และเรารู้จักไลบรารีและ API ที่มีอยู่เป็นอย่างดีอยู่แล้ว สิ่งที่ยากจริงๆ คือการใช้มันเพื่อพัฒนาและบำรุงรักษาแอปพลิเคชันระดับองค์กร
เราจำเป็นต้องตรวจสอบให้แน่ใจว่าเพื่อนร่วมงานปิดการเชื่อมต่อฐานข้อมูลในเวลาที่ถูกต้อง พวกเขาไม่ได้ครอบครองธุรกรรมต่อไป ข้อยกเว้นได้รับการจัดการอย่างถูกต้องในเลเยอร์ที่เหมาะสม การรับและปลดล็อคการล็อคอย่างถูกต้อง ฯลฯ
หากพิจารณาเป็นรายบุคคล ปัญหาเหล่านี้ไม่ใช่เรื่องใหญ่ อย่างไรก็ตาม เมื่อรวมกับความซับซ้อนของภาคสนาม ปัญหาก็จะยากมาก ทรัพยากรในการพัฒนาก็คับแคบ และการบำรุงรักษาก็ทำได้ยาก
จะเกิดอะไรขึ้นถ้าเราสรุปกลยุทธ์เหล่านี้ออกเป็นโค้ดเล็กๆ หลายๆ ชิ้น และปล่อยให้กลยุทธ์เหล่านี้ดำเนินการจัดการข้อจำกัดอย่างอิสระ ดังนั้นเราจึงไม่ต้องใช้พลังงานอย่างต่อเนื่องเพื่อดำเนินกลยุทธ์ นี่เป็นการปรับปรุงครั้งใหญ่ มาดูกันว่า Functional Programming ทำงานอย่างไร
การวนซ้ำที่บ้าคลั่ง
เราได้เขียนการทำซ้ำต่างๆ เพื่อประมวลผลรายการ ชุด และแผนที่ การใช้ตัววนซ้ำใน Java เป็นเรื่องปกติมาก แต่ก็ซับซ้อนเกินไป ไม่เพียงแต่ใช้โค้ดหลายบรรทัดเท่านั้น แต่ยังสรุปได้ยากอีกด้วย
เราจะวนซ้ำคอลเลกชันและพิมพ์ได้อย่างไร คุณสามารถใช้ for วนซ้ำได้ เราจะกรององค์ประกอบบางอย่างออกจากคอลเลกชันได้อย่างไร ยังคงใช้ for loop แต่คุณต้องเพิ่มตัวแปรที่แก้ไขได้เพิ่มเติม หลังจากเลือกค่าเหล่านี้แล้ว จะใช้หาค่าสุดท้ายเช่นค่าต่ำสุด ค่าสูงสุด ค่าเฉลี่ย เป็นต้น ได้อย่างไร? จากนั้นคุณจะต้องรีไซเคิลและแก้ไขตัวแปร
การวนซ้ำแบบนี้เป็นเหมือนยาครอบจักรวาล ทำได้ทุกอย่าง แต่ทุกอย่างก็เบาบาง ขณะนี้ Java มีตัววนซ้ำในตัวสำหรับการดำเนินการหลายอย่าง เช่น การดำเนินการที่ทำเฉพาะลูป การดำเนินการแมป การกรองค่า การดำเนินการที่ลดการดำเนินการ และมีฟังก์ชันที่สะดวกมากมาย เช่น สูงสุด ต่ำสุด และ เฉลี่ย ฯลฯ นอกจากนี้ การดำเนินการเหล่านี้สามารถนำมารวมกันได้เป็นอย่างดี ดังนั้นเราจึงสามารถปะติดปะต่อกันเพื่อใช้ตรรกะทางธุรกิจ ซึ่งง่ายและใช้โค้ดน้อยลง ยิ่งไปกว่านั้น รหัสที่เขียนสามารถอ่านได้สูง เนื่องจากมีความสอดคล้องกับลำดับการอธิบายปัญหาในทางตรรกะ เราจะเห็นตัวอย่างหลายประการในบทที่ 2 การใช้คอลเลคชัน ในหน้า 19 และหนังสือเล่มนี้เต็มไปด้วยตัวอย่างดังกล่าว
ใช้กลยุทธ์
มีการนำนโยบายไปใช้ทั่วทั้งแอปพลิเคชันทั่วทั้งองค์กร ตัวอย่างเช่น เราต้องยืนยันว่าการดำเนินการได้รับการตรวจสอบความถูกต้องเพื่อความปลอดภัยอย่างถูกต้อง เราต้องแน่ใจว่าธุรกรรมสามารถดำเนินการได้อย่างรวดเร็ว และบันทึกการแก้ไขได้รับการอัปเดตอย่างถูกต้อง งานเหล่านี้มักจะจบลงด้วยการเป็นโค้ดธรรมดาๆ บนฝั่งเซิร์ฟเวอร์ คล้ายกับโค้ดเทียมต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ธุรกรรมธุรกรรม = getFromTransactionFactory();
//... การดำเนินการเพื่อรันภายในธุรกรรม ...
ตรวจสอบความคืบหน้าและมอบหมายหรือย้อนกลับธุรกรรม();
UpdateAuditTrail();
มีสองปัญหาเกี่ยวกับวิธีการนี้ ประการแรก มักส่งผลให้เกิดความซ้ำซ้อนของความพยายาม และยังเพิ่มค่าบำรุงรักษาด้วย ประการที่สอง มันง่ายที่จะลืมเกี่ยวกับข้อยกเว้นที่อาจมีอยู่ในรหัสธุรกิจ ซึ่งอาจส่งผลกระทบต่อวงจรชีวิตของธุรกรรมและการอัปเดตบันทึกการแก้ไข สิ่งนี้ควรดำเนินการโดยใช้การลองและบล็อกในที่สุด แต่ทุกครั้งที่มีคนแตะโค้ดนี้ เราต้องยืนยันอีกครั้งว่ากลยุทธ์นี้ไม่ถูกทำลาย
มีอีกวิธีหนึ่งคือเราสามารถลบโรงงานออกแล้ววางโค้ดนี้ไว้ข้างหน้าได้ แทนที่จะรับอ็อบเจ็กต์ธุรกรรม ให้ส่งโค้ดที่ดำเนินการไปยังฟังก์ชันที่ได้รับการดูแลอย่างดี เช่นนี้
คัดลอกรหัสรหัสดังต่อไปนี้:
runWithinTransaction ((ธุรกรรมธุรกรรม) -> {
//... การดำเนินการเพื่อรันภายในธุรกรรม ...
-
มันเป็นก้าวเล็กๆ สำหรับคุณ แต่ช่วยให้คุณประหยัดปัญหาได้มาก กลยุทธ์ในการตรวจสอบสถานะและอัปเดตบันทึกในเวลาเดียวกันจะถูกสรุปและสรุปไว้ในเมธอด runWithinTransaction เราส่งโค้ดวิธีการนี้ที่ต้องใช้ในบริบทของธุรกรรม เราไม่ต้องกังวลอีกต่อไปว่ามีคนลืมทำตามขั้นตอนนี้หรือจัดการข้อยกเว้นไม่ถูกต้องอีกต่อไป ฟังก์ชันที่ใช้นโยบายได้ดูแลเรื่องนั้นแล้ว
เราจะกล่าวถึงวิธีใช้นิพจน์แลมบ์ดาเพื่อนำกลยุทธ์นี้ไปใช้ในบทที่ 5
กลยุทธ์การขยายตัว
กลยุทธ์ดูเหมือนจะมีอยู่ทั่วไป นอกเหนือจากการนำไปใช้แล้ว แอปพลิเคชันระดับองค์กรยังต้องขยายอีกด้วย เราหวังว่าจะเพิ่มหรือลบการดำเนินการบางอย่างผ่านข้อมูลการกำหนดค่าบางอย่าง กล่าวคือ เราสามารถประมวลผลการดำเนินการเหล่านั้นก่อนที่จะดำเนินการตรรกะหลักของโมดูล นี่เป็นเรื่องธรรมดามากใน Java แต่จำเป็นต้องคิดและออกแบบล่วงหน้า
ส่วนประกอบที่จำเป็นต้องขยายมักจะมีอินเทอร์เฟซตั้งแต่หนึ่งรายการขึ้นไป เราจำเป็นต้องออกแบบอินเทอร์เฟซและโครงสร้างลำดับชั้นของคลาสการใช้งานอย่างระมัดระวัง สิ่งนี้อาจทำงานได้ดี แต่จะทำให้คุณมีอินเทอร์เฟซและคลาสมากมายที่ต้องได้รับการดูแล การออกแบบดังกล่าวอาจเทอะทะและบำรุงรักษาได้ยาก ซึ่งท้ายที่สุดก็เอาชนะจุดประสงค์ของการปรับขนาดตั้งแต่แรกได้
มีวิธีแก้ไขปัญหาอื่น - อินเทอร์เฟซการทำงานและนิพจน์แลมบ์ดา ซึ่งเราสามารถใช้เพื่อออกแบบกลยุทธ์ที่ปรับขนาดได้ เราไม่จำเป็นต้องสร้างอินเทอร์เฟซใหม่หรือใช้ชื่อเมธอดเดียวกัน เราสามารถมุ่งเน้นที่ตรรกะทางธุรกิจที่จะนำไปใช้ได้มากขึ้น ซึ่งเราจะกล่าวถึงในการใช้นิพจน์แลมบ์ดาสำหรับการตกแต่งในหน้า 73
การทำงานพร้อมกันทำได้ง่าย
แอปพลิเคชันขนาดใหญ่กำลังใกล้ถึงหลักชัยสำคัญเมื่อเกิดปัญหาด้านประสิทธิภาพร้ายแรงอย่างกะทันหัน ทีมงานระบุอย่างรวดเร็วว่าปัญหาคอขวดด้านประสิทธิภาพอยู่ในโมดูลขนาดใหญ่ที่ประมวลผลข้อมูลจำนวนมหาศาล บางคนในทีมแนะนำว่าประสิทธิภาพของระบบสามารถปรับปรุงได้หากสามารถใช้ประโยชน์จากมัลติคอร์ได้อย่างเต็มที่ อย่างไรก็ตาม หากโมดูลขนาดใหญ่นี้เขียนในรูปแบบ Java แบบเก่า ความสุขที่ได้รับจากคำแนะนำนี้จะถูกทำลายในไม่ช้า
ทีมงานตระหนักได้อย่างรวดเร็วว่าการเปลี่ยนยักษ์ใหญ่นี้จากการดำเนินการแบบอนุกรมไปเป็นการดำเนินการแบบขนานจะต้องใช้ความพยายามอย่างมาก เพิ่มความซับซ้อนเป็นพิเศษ และทำให้เกิด BUG ที่เกี่ยวข้องกับมัลติเธรดได้อย่างง่ายดาย ไม่มีวิธีใดที่ดีกว่าในการปรับปรุงประสิทธิภาพใช่ไหม
เป็นไปได้ไหมที่โค้ดแบบอนุกรมและแบบขนานจะเหมือนกัน ไม่ว่าคุณจะเลือกการดำเนินการแบบอนุกรมหรือแบบขนาน เช่นเดียวกับการกดสวิตช์และแสดงความคิดของคุณ
ดูเหมือนว่าสิ่งนี้จะเกิดขึ้นได้เฉพาะในนาร์เนียเท่านั้น แต่ถ้าเราพัฒนาในแง่ของการใช้งานอย่างสมบูรณ์ ทั้งหมดนี้จะกลายเป็นความจริง ตัววนซ้ำและรูปแบบการทำงานในตัวจะขจัดอุปสรรคสุดท้ายในการขนาน การออกแบบ JDK ช่วยให้สามารถสลับระหว่างการดำเนินการแบบอนุกรมและแบบขนานโดยมีการเปลี่ยนแปลงโค้ดที่ไม่เด่นชัดเพียงไม่กี่ครั้ง ซึ่งเราจะกล่าวถึงใน "การก้าวกระโดดไปสู่การทำงานแบบขนาน" ในหน้า 145
เล่าเรื่อง
มีหลายสิ่งที่สูญเสียไปในกระบวนการเปลี่ยนข้อกำหนดทางธุรกิจให้เป็นการนำโค้ดไปใช้ ยิ่งสูญเสียไปมากเท่าใด โอกาสที่จะเกิดข้อผิดพลาดและค่าใช้จ่ายในการจัดการก็จะยิ่งสูงขึ้นเท่านั้น หากโค้ดดูเหมือนอธิบายข้อกำหนด ก็จะอ่านง่ายขึ้น หารือกับผู้คนเกี่ยวกับข้อกำหนดได้ง่ายขึ้น และตอบสนองความต้องการของพวกเขาได้ง่ายขึ้น
ตัวอย่างเช่น คุณได้ยินผู้จัดการผลิตภัณฑ์พูดว่า "รับราคาของหุ้นทั้งหมด ค้นหาราคาที่มากกว่า 500 หยวน และคำนวณสินทรัพย์ทั้งหมดที่สามารถจ่ายเงินปันผลได้" การใช้สิ่งอำนวยความสะดวกใหม่ที่ Java มีให้ คุณสามารถเขียน:
คัดลอกรหัสรหัสดังต่อไปนี้:
tickers.map(StockUtil::getprice).filter(StockUtil::priceIsLessThan500).sum()
กระบวนการแปลงนี้แทบไม่สูญเสียเลย เนื่องจากโดยพื้นฐานแล้วไม่มีอะไรต้องแปลง นี่คือรูปแบบการใช้งานจริง และคุณจะเห็นตัวอย่างอื่นๆ อีกมากมายตลอดทั้งเล่ม โดยเฉพาะบทที่ 8 การสร้างโปรแกรมด้วย Lambda Expressions หน้า 137
มุ่งเน้นไปที่การกักกัน
ในการพัฒนาระบบ ธุรกิจหลักและตรรกะที่ละเอียดที่ต้องการนั้นมักจะต้องถูกแยกออกจากกัน ตัวอย่างเช่น ระบบประมวลผลคำสั่งซื้ออาจต้องการใช้กลยุทธ์การเก็บภาษีที่แตกต่างกันสำหรับแหล่งที่มาของธุรกรรมที่แตกต่างกัน การแยกการคำนวณภาษีออกจากตรรกะการประมวลผลที่เหลือทำให้โค้ดสามารถนำมาใช้ซ้ำและปรับขนาดได้มากขึ้น
ในการเขียนโปรแกรมเชิงวัตถุ เราเรียกการแยกความกังวลนี้ และมักจะใช้รูปแบบกลยุทธ์เพื่อแก้ไขปัญหานี้ โดยทั่วไปวิธีแก้ปัญหาคือการสร้างอินเทอร์เฟซและคลาสการใช้งานบางส่วน
เราสามารถบรรลุผลแบบเดียวกันได้โดยใช้โค้ดน้อยลง นอกจากนี้เรายังสามารถลองใช้แนวคิดผลิตภัณฑ์ของเราเองได้อย่างรวดเร็วโดยไม่ต้องมีโค้ดมากมายและหยุดนิ่ง เราจะสำรวจเพิ่มเติมถึงวิธีสร้างรูปแบบนี้และดำเนินการแยกข้อกังวลผ่านฟังก์ชันแบบน้ำหนักเบาในการแยกข้อกังวลโดยใช้ Lambda Expressions ในหน้า 63
การประเมินขี้เกียจ
เมื่อพัฒนาแอปพลิเคชันระดับองค์กร เราอาจโต้ตอบกับบริการของเว็บ ฐานข้อมูลการโทร ประมวลผล XML ฯลฯ มีการดำเนินการหลายอย่างที่เราจำเป็นต้องดำเนินการ แต่ไม่ใช่ทั้งหมดที่จำเป็นตลอดเวลา การหลีกเลี่ยงการดำเนินการบางอย่างหรืออย่างน้อยก็ชะลอการดำเนินการที่ไม่จำเป็นชั่วคราวเป็นวิธีที่ง่ายที่สุดวิธีหนึ่งในการปรับปรุงประสิทธิภาพหรือลดการเริ่มต้นโปรแกรมและเวลาตอบสนอง
นี่เป็นเพียงสิ่งเล็กๆ แต่ต้องใช้ความพยายามอย่างมากในการปรับใช้ด้วยวิธี OOP ล้วนๆ เพื่อชะลอการเริ่มต้นของอ็อบเจ็กต์เฮฟวี่เวทบางรายการ เราต้องจัดการการอ้างอิงอ็อบเจ็กต์ต่างๆ ตรวจสอบตัวชี้ว่าง ฯลฯ
อย่างไรก็ตาม หากคุณใช้คลาส Optinal ใหม่และ API รูปแบบการทำงานบางอย่างที่มีให้ กระบวนการนี้จะง่ายมาก และโค้ดจะชัดเจนยิ่งขึ้น เราจะพูดถึงเรื่องนี้ในการเริ่มต้นแบบ Lazy ในหน้า 105
ปรับปรุงความสามารถในการทดสอบ
ยิ่งโค้ดมีตรรกะในการประมวลผลน้อยเท่าใด ข้อผิดพลาดก็จะยิ่งได้รับการแก้ไขน้อยลงเท่านั้น โดยทั่วไปแล้ว รหัสการทำงานจะแก้ไขได้ง่ายกว่าและทดสอบได้ง่ายกว่า
นอกจากนี้ เช่นเดียวกับบทที่ 4 การออกแบบด้วยนิพจน์ Lambda และบทที่ 5 การใช้ทรัพยากร นิพจน์ lambda สามารถใช้เป็นออบเจ็กต์จำลองน้ำหนักเบาเพื่อทำให้การทดสอบข้อยกเว้นชัดเจนและเข้าใจง่ายขึ้น นิพจน์ Lambda ยังสามารถใช้เป็นตัวช่วยในการทดสอบที่ดีได้อีกด้วย กรณีทดสอบทั่วไปหลายกรณีสามารถยอมรับและจัดการนิพจน์แลมบ์ดาได้ กรณีทดสอบที่เขียนในลักษณะนี้สามารถรวบรวมสาระสำคัญของฟังก์ชันการทำงานที่ต้องได้รับการทดสอบการถดถอย ในเวลาเดียวกัน การใช้งานต่างๆ ที่ต้องทดสอบสามารถทำได้โดยการส่งนิพจน์แลมบ์ดาที่แตกต่างกัน
กรณีทดสอบอัตโนมัติของ JDK เองก็เป็นตัวอย่างการใช้งานที่ดีของนิพจน์ lambda หากคุณต้องการทราบข้อมูลเพิ่มเติม คุณสามารถดูซอร์สโค้ดในที่เก็บ OpenJDK ได้ ด้วยโปรแกรมทดสอบเหล่านี้ คุณจะเห็นว่านิพจน์แลมบ์ดากำหนดพารามิเตอร์พฤติกรรมหลักของกรณีทดสอบได้อย่างไร ตัวอย่างเช่น นิพจน์เหล่านี้สร้างโปรแกรมทดสอบในลักษณะนี้ "สร้างคอนเทนเนอร์สำหรับผลลัพธ์" จากนั้น "เพิ่มการตรวจสอบเงื่อนไขภายหลังที่กำหนดพารามิเตอร์"
เราได้เห็นแล้วว่าการเขียนโปรแกรมเชิงฟังก์ชันไม่เพียงแต่ช่วยให้เราเขียนโค้ดคุณภาพสูงเท่านั้น แต่ยังช่วยแก้ปัญหาต่างๆ ได้อย่างหรูหราในระหว่างกระบวนการพัฒนาอีกด้วย ซึ่งหมายความว่าการพัฒนาโปรแกรมจะเร็วขึ้นและง่ายขึ้น โดยมีข้อผิดพลาดน้อยลง ตราบใดที่คุณปฏิบัติตามหลักเกณฑ์บางประการที่เราจะแนะนำในภายหลัง
ส่วนที่ 4: วิวัฒนาการไม่ใช่การปฏิวัติ
เราไม่จำเป็นต้องเปลี่ยนเป็นภาษาอื่นเพื่อเพลิดเพลินกับประโยชน์ของการเขียนโปรแกรมเชิงฟังก์ชัน สิ่งเดียวที่เราต้องเปลี่ยนคือวิธีที่เราใช้ Java ภาษาเช่น C++, Java และ C# ทั้งหมดรองรับการเขียนโปรแกรมเชิงความจำเป็นและเชิงวัตถุ แต่ตอนนี้พวกเขากำลังเริ่มยอมรับการเขียนโปรแกรมเชิงฟังก์ชันแล้ว เราเพิ่งดูโค้ดทั้งสองสไตล์และหารือถึงคุณประโยชน์ที่ Functional Programming สามารถนำมาได้ ตอนนี้เรามาดูแนวคิดหลักและตัวอย่างบางส่วนเพื่อช่วยเราเรียนรู้สไตล์ใหม่นี้
ทีมพัฒนาภาษา Java ใช้เวลาและความพยายามอย่างมากในการเพิ่มความสามารถด้านการเขียนโปรแกรมเชิงฟังก์ชันให้กับภาษา Java และ JDK หากต้องการเพลิดเพลินไปกับผลประโยชน์ที่ได้รับ เราต้องแนะนำแนวคิดใหม่บางประการก่อน เราสามารถปรับปรุงคุณภาพของโค้ดของเราได้ตราบใดที่เราปฏิบัติตามกฎต่อไปนี้:
1. ประกาศ
2. ส่งเสริมความไม่เปลี่ยนรูป
3. หลีกเลี่ยงผลข้างเคียง
4. ชอบการแสดงออกมากกว่าข้อความ
5. การออกแบบโดยใช้ฟังก์ชันลำดับที่สูงกว่า
มาดูแนวทางปฏิบัติเหล่านี้กัน
ประกาศ
แก่นของสิ่งที่เราคุ้นเคยในฐานะการเขียนโปรแกรมที่จำเป็นคือความแปรปรวนและการเขียนโปรแกรมที่ขับเคลื่อนด้วยคำสั่ง เราสร้างตัวแปรแล้วแก้ไขค่าของมันอย่างต่อเนื่อง นอกจากนี้เรายังให้คำแนะนำโดยละเอียดในการดำเนินการ เช่น การสร้างแฟล็กดัชนีของการวนซ้ำ การเพิ่มค่า การตรวจสอบว่าลูปสิ้นสุดแล้วหรือไม่ การอัปเดตองค์ประกอบ Nth ของอาร์เรย์ เป็นต้น ในอดีตเนื่องจากข้อจำกัดของลักษณะของเครื่องมือและฮาร์ดแวร์ เราจึงสามารถเขียนโค้ดได้ด้วยวิธีนี้เท่านั้น เรายังเห็นอีกว่าในคอลเล็กชั่นที่ไม่เปลี่ยนรูป วิธีการ declarative contains นั้นใช้งานง่ายกว่าคอลเลกชั่นที่จำเป็น ปัญหาที่ยากลำบากและการดำเนินงานระดับต่ำทั้งหมดถูกนำไปใช้ในฟังก์ชันห้องสมุด และเราไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียดเหล่านี้อีกต่อไป เพื่อความเรียบง่าย เราควรใช้การเขียนโปรแกรมแบบประกาศด้วย ความไม่เปลี่ยนรูปและการเขียนโปรแกรมแบบประกาศเป็นสาระสำคัญของการเขียนโปรแกรมเชิงฟังก์ชัน และในที่สุด Java ก็ทำให้มันเป็นจริงได้
ส่งเสริมความไม่เปลี่ยนรูป
โค้ดที่มีตัวแปรที่ไม่แน่นอนจะมีกิจกรรมหลายเส้นทาง ยิ่งคุณเปลี่ยนแปลงสิ่งต่าง ๆ มากเท่าไร การทำลายโครงสร้างเดิมก็จะยิ่งง่ายขึ้นและทำให้เกิดข้อผิดพลาดมากขึ้นเท่านั้น โค้ดที่มีการแก้ไขตัวแปรหลายตัวนั้นเข้าใจยากและขนานกันได้ยาก ความไม่เปลี่ยนรูปช่วยขจัดความกังวลเหล่านี้ได้อย่างแท้จริง Java รองรับความไม่เปลี่ยนรูปแต่ไม่ได้ต้องการ แต่เราทำได้ เราจำเป็นต้องเปลี่ยนนิสัยเก่าในการปรับเปลี่ยนสถานะของวัตถุ เราควรใช้วัตถุที่ไม่เปลี่ยนรูปให้มากที่สุด เมื่อประกาศตัวแปร สมาชิก และพารามิเตอร์ ให้พยายามประกาศให้เป็นตัวแปรสุดท้าย เช่นเดียวกับคำพูดอันโด่งดังของ Joshua Bloch ใน "Effective Java" "ถือว่าวัตถุไม่เปลี่ยนรูป" เมื่อสร้างวัตถุ ให้พยายามสร้างวัตถุที่ไม่เปลี่ยนรูป เช่น สตริง เมื่อสร้างคอลเลกชัน ให้ลองสร้างคอลเลกชันที่ไม่เปลี่ยนรูปหรือแก้ไขไม่ได้ เช่น การใช้เมธอด เช่น Arrays.asList() และ unmodifiableList() ของ Collections ด้วยการหลีกเลี่ยงความแปรปรวน เราสามารถเขียนฟังก์ชันล้วนๆ ได้ นั่นคือฟังก์ชันที่ไม่มีผลข้างเคียง
หลีกเลี่ยงผลข้างเคียง
สมมติว่าคุณกำลังเขียนโค้ดเพื่อดึงราคาหุ้นจากอินเทอร์เน็ตและเขียนลงในตัวแปรที่ใช้ร่วมกัน หากเรามีราคาที่ต้องดึงข้อมูลจำนวนมาก เราจะต้องดำเนินการที่ใช้เวลานานเหล่านี้ตามลำดับ หากเราต้องการใช้ประโยชน์จากพลังของมัลติเธรด เราต้องจัดการกับความยุ่งยากของเธรดและการซิงโครไนซ์เพื่อป้องกันสภาวะการแข่งขัน ผลลัพธ์สุดท้ายคือประสิทธิภาพของโปรแกรมแย่มาก และคนลืมกินและนอนเพื่อรักษาเส้นด้าย หากกำจัดผลข้างเคียง เราก็สามารถหลีกเลี่ยงปัญหาเหล่านี้ได้อย่างสมบูรณ์ ฟังก์ชันที่ไม่มีผลข้างเคียงส่งเสริมความไม่เปลี่ยนรูป และไม่แก้ไขอินพุตหรือสิ่งอื่นใดภายในขอบเขต ฟังก์ชันประเภทนี้สามารถอ่านได้ง่าย มีข้อผิดพลาดเล็กน้อย และปรับแต่งได้ง่าย เนื่องจากไม่มีผลข้างเคียง จึงไม่จำเป็นต้องกังวลเกี่ยวกับสภาพการแข่งขันหรือการปรับเปลี่ยนที่เกิดขึ้นพร้อมกัน ไม่เพียงเท่านั้น เรายังดำเนินการฟังก์ชันเหล่านี้แบบขนานได้อย่างง่ายดาย ซึ่งเราจะกล่าวถึงในหน้า 145
ชอบการแสดงออก
ข้อความเป็นเหมือนมันฝรั่งร้อนเพราะมันบังคับให้มีการปรับเปลี่ยน นิพจน์ส่งเสริมความไม่เปลี่ยนรูปและองค์ประกอบของฟังก์ชัน ตัวอย่างเช่น ขั้นแรกเราใช้คำสั่ง for เพื่อคำนวณราคารวมหลังหักส่วนลด รหัสดังกล่าวนำไปสู่ความแปรปรวนและรหัสรายละเอียด การใช้แผนที่และวิธีการรวมเวอร์ชันที่ชัดเจนและประกาศมากขึ้นไม่เพียงแต่หลีกเลี่ยงการดำเนินการแก้ไขเท่านั้น แต่ยังช่วยให้ฟังก์ชันเชื่อมโยงเข้าด้วยกันอีกด้วย เมื่อเขียนโค้ด คุณควรพยายามใช้นิพจน์แทนคำสั่ง ทำให้โค้ดง่ายขึ้นและเข้าใจง่ายขึ้น รหัสจะถูกดำเนินการตามตรรกะทางธุรกิจ เช่นเดียวกับเมื่อเราอธิบายปัญหา เวอร์ชันที่กระชับนั้นง่ายต่อการปรับเปลี่ยนอย่างไม่ต้องสงสัยหากข้อกำหนดเปลี่ยนแปลง
การออกแบบโดยใช้ฟังก์ชันลำดับที่สูงกว่า
Java ไม่ได้บังคับใช้ความไม่เปลี่ยนรูปเหมือนภาษาเชิงฟังก์ชันเช่น Haskell แต่ช่วยให้เราแก้ไขตัวแปรได้ ดังนั้น Java จึงไม่ใช่และจะไม่มีวันเป็นภาษาโปรแกรมที่ใช้งานได้เพียงอย่างเดียว อย่างไรก็ตาม เราสามารถใช้ฟังก์ชันที่มีลำดับสูงกว่าสำหรับการเขียนโปรแกรมฟังก์ชันใน Java ได้ ฟังก์ชันที่มีลำดับสูงกว่าจะถูกนำมาใช้ซ้ำในระดับถัดไป ด้วยฟังก์ชันที่มีลำดับสูง เราสามารถนำโค้ดที่เป็นผู้ใหญ่ซึ่งมีขนาดเล็ก เชี่ยวชาญ และมีความเหนียวแน่นสูงกลับมาใช้ใหม่ได้อย่างง่ายดาย ใน OOP เราคุ้นเคยกับการส่งวัตถุไปยังวิธีการ สร้างวัตถุใหม่ในวิธีการ แล้วส่งกลับวัตถุ ฟังก์ชันที่มีลำดับสูงกว่าจะทำสิ่งเดียวกันกับฟังก์ชันที่เมธอดทำกับอ็อบเจ็กต์ ด้วยฟังก์ชันลำดับที่สูงกว่าที่เราสามารถทำได้
1. ส่งผ่านฟังก์ชันไปยังฟังก์ชัน
2. สร้างฟังก์ชันใหม่ภายในฟังก์ชัน
3. ส่งกลับฟังก์ชันภายในฟังก์ชัน
เราได้เห็นตัวอย่างการส่งพารามิเตอร์จากฟังก์ชันหนึ่งไปยังฟังก์ชันอื่นแล้ว และต่อมาเราจะเห็นตัวอย่างการสร้างและส่งกลับฟังก์ชัน ลองดูตัวอย่าง "การส่งพารามิเตอร์ไปยังฟังก์ชัน" อีกครั้ง:
คัดลอกรหัสรหัสดังต่อไปนี้:
ราคา.สตรีม()
.filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9)))
รายงานข้อผิดพลาด • อภิปราย
.reduce(BigDecimal.ZERO, BigDecimal::เพิ่ม);
ในโค้ดนี้ เราจะส่งฟังก์ชัน price -> price.multiply(BigDecimal.valueOf(0.9)) ไปยังฟังก์ชัน map ฟังก์ชันที่ส่งผ่านจะถูกสร้างขึ้นเมื่อมีการเรียกใช้แผนผังฟังก์ชันที่มีลำดับสูงกว่า โดยทั่วไปแล้ว ฟังก์ชันจะมีเนื้อความของฟังก์ชัน ชื่อฟังก์ชัน รายการพารามิเตอร์ และค่าที่ส่งคืน ฟังก์ชันนี้สร้างขึ้นทันทีมีรายการพารามิเตอร์ตามด้วยลูกศร (->) แล้วตามด้วยเนื้อหาฟังก์ชันแบบสั้น ประเภทของพารามิเตอร์ถูกอนุมานโดยคอมไพลเลอร์ Java และประเภทการส่งคืนก็เป็นแบบนัยเช่นกัน นี่เป็นฟังก์ชันที่ไม่ระบุชื่อ แต่ไม่มีชื่อ แต่เราไม่เรียกมันว่าฟังก์ชันนิรนาม เราเรียกมันว่านิพจน์แลมบ์ดา การส่งผ่านฟังก์ชันที่ไม่ระบุชื่อเป็นพารามิเตอร์ไม่ใช่เรื่องใหม่ใน Java เรามักจะผ่านคลาสภายในที่ไม่ระบุชื่อมาก่อน แม้ว่าคลาสที่ไม่เปิดเผยตัวตนจะมีเพียงวิธีเดียว แต่เรายังคงต้องผ่านขั้นตอนการสร้างคลาสและยกตัวอย่างมัน ด้วยนิพจน์แลมบ์ดา เราจึงสามารถเพลิดเพลินไปกับไวยากรณ์แบบไลท์เวทได้ ไม่เพียงเท่านั้น เรามักจะคุ้นเคยกับการสรุปแนวคิดบางอย่างเป็นวัตถุต่างๆ แต่ตอนนี้เราสามารถสรุปพฤติกรรมบางอย่างเป็นนิพจน์แลมบ์ดาได้ การเขียนโปรแกรมด้วยรูปแบบการเขียนโค้ดนี้ยังต้องมีการคิดอยู่บ้าง เราต้องเปลี่ยนการคิดเชิงบังคับที่ฝังแน่นอยู่แล้วให้กลายเป็นการคิดเชิงปฏิบัติ มันอาจจะเจ็บปวดเล็กน้อยในช่วงแรก แต่ในไม่ช้า คุณจะคุ้นเคยกับมันมากขึ้น เมื่อคุณเจาะลึกลงไปเรื่อยๆ API ที่ไม่สามารถใช้งานได้เหล่านั้นก็จะค่อยๆ ถูกทิ้งไว้เบื้องหลัง เรามาหยุดหัวข้อนี้กันก่อน มาดูกันว่า Java จัดการกับ lambda expression อย่างไร เราเคยส่งอ็อบเจ็กต์ไปยังเมธอดเสมอ แต่ตอนนี้เราสามารถจัดเก็บฟังก์ชันและส่งผ่านพวกมันไปได้ มาดูความลับเบื้องหลังความสามารถของ Java ในการใช้ฟังก์ชันเป็นพารามิเตอร์กัน
ส่วนที่ 5: เพิ่มน้ำตาลไวยากรณ์บางส่วน
สิ่งนี้สามารถทำได้โดยใช้ฟังก์ชันดั้งเดิมของ Java แต่นิพจน์แลมบ์ดาจะเพิ่มน้ำตาลเชิงวากยสัมพันธ์ ช่วยลดขั้นตอนบางอย่างและทำให้งานของเราง่ายขึ้น โค้ดที่เขียนในลักษณะนี้ไม่เพียงแต่พัฒนาเร็วขึ้น แต่ยังแสดงความคิดของเราได้ดีขึ้นอีกด้วย อินเทอร์เฟซจำนวนมากที่เราใช้ในอดีตมีเพียงวิธีเดียว: Runnable, Callable ฯลฯ อินเทอร์เฟซเหล่านี้สามารถพบได้ทุกที่ในไลบรารี JDK และที่ที่มีการใช้งาน มักจะสามารถทำได้โดยใช้ฟังก์ชัน ฟังก์ชันไลบรารีที่ก่อนหน้านี้ต้องการเพียงอินเทอร์เฟซแบบเดียวสามารถส่งผ่านฟังก์ชันแบบน้ำหนักเบาได้ ต้องขอบคุณน้ำตาลทางวากยสัมพันธ์ที่ได้รับจากอินเทอร์เฟซเชิงฟังก์ชัน ส่วนต่อประสานการทำงานเป็นส่วนต่อประสานที่มีวิธีนามธรรมเพียงวิธีเดียวเท่านั้น ดูอินเทอร์เฟซเหล่านั้นด้วยวิธีเดียวเท่านั้น Runnable, Callable ฯลฯ คำจำกัดความนี้ใช้กับอินเทอร์เฟซเหล่านี้ มีอินเทอร์เฟซดังกล่าวเพิ่มเติมใน JDK8 - ฟังก์ชั่น, เพรดิเคต, ผู้บริโภค, ซัพพลายเออร์ ฯลฯ (หน้า 157 ภาคผนวก 1 มีรายการอินเทอร์เฟซที่มีรายละเอียดเพิ่มเติม) อินเทอร์เฟซการทำงานสามารถมีวิธีการคงที่และวิธีการเริ่มต้นได้หลายวิธี ซึ่งนำมาใช้ในอินเทอร์เฟซ เราสามารถใช้คำอธิบายประกอบ @FunctionalInterface เพื่อใส่คำอธิบายประกอบอินเทอร์เฟซการทำงานได้ คอมไพเลอร์ไม่ได้ใช้คำอธิบายประกอบนี้ แต่สามารถระบุประเภทของอินเทอร์เฟซนี้ได้ชัดเจนยิ่งขึ้น ไม่เพียงแค่นั้นถ้าเราใส่คำอธิบายประกอบอินเทอร์เฟซด้วยคำอธิบายประกอบนี้คอมไพเลอร์จะตรวจสอบอย่างแข็งขันว่ามันสอดคล้องกับกฎของอินเทอร์เฟซที่ใช้งานได้หรือไม่ หากวิธีการรับอินเตอร์เฟสการทำงานเป็นพารามิเตอร์พารามิเตอร์ที่เราสามารถผ่านได้รวมถึง:
1. ชั้นเรียนภายในที่ไม่ระบุชื่อวิธีที่เก่าแก่ที่สุด
2. นิพจน์แลมบ์ดาเหมือนกับที่เราทำในวิธีแผนที่
3. การอ้างอิงถึงวิธีการหรือตัวสร้าง (เราจะพูดถึงมันในภายหลัง)
หากพารามิเตอร์วิธีการเป็นอินเตอร์เฟสที่ใช้งานได้คอมไพเลอร์จะยอมรับการแสดงออกหรือการอ้างอิงวิธีการของแลมบ์ดาอย่างมีความสุขเป็นพารามิเตอร์ หากเราผ่านนิพจน์แลมบ์ดาไปยังวิธีการคอมไพเลอร์จะแปลงนิพจน์เป็นอินสแตนซ์ของอินเทอร์เฟซการทำงานที่สอดคล้องกันก่อน การเปลี่ยนแปลงนี้เป็นมากกว่าแค่การสร้างชั้นเรียนภายใน วิธีการของอินสแตนซ์ที่สร้างขึ้นแบบซิงโครนัสนี้สอดคล้องกับวิธีนามธรรมของอินเทอร์เฟซการทำงานของพารามิเตอร์ ตัวอย่างเช่นวิธีการแผนที่จะได้รับฟังก์ชันอินเตอร์เฟสฟังก์ชันเป็นพารามิเตอร์ เมื่อเรียกใช้วิธีแผนที่คอมไพเลอร์ Java จะสร้างแบบซิงโครนัสดังแสดงในรูปด้านล่าง
พารามิเตอร์ของนิพจน์แลมบ์ดาจะต้องตรงกับพารามิเตอร์ของวิธีนามธรรมของอินเตอร์เฟส วิธีการที่สร้างขึ้นนี้จะส่งคืนผลลัพธ์ของนิพจน์แลมบ์ดา หากประเภทการส่งคืนไม่ตรงกับวิธีนามธรรมโดยตรงวิธีนี้จะแปลงค่าส่งคืนเป็นประเภทที่เหมาะสม เรามีภาพรวมของการแสดงออกของแลมบ์ดาไปยังวิธีการต่างๆ มาทบทวนสิ่งที่เราเพิ่งพูดถึงอย่างรวดเร็วแล้วเริ่มการสำรวจของ Lambda Expressions
สรุป
นี่เป็นพื้นที่ใหม่ของชวา ผ่านฟังก์ชั่นที่มีลำดับสูงกว่าตอนนี้เราสามารถเขียนรหัสสไตล์การทำงานที่หรูหราและคล่องแคล่ว รหัสที่เขียนด้วยวิธีนี้มีความรัดกุมและเข้าใจง่ายมีข้อผิดพลาดเล็กน้อยและเอื้อต่อการบำรุงรักษาและการขนาน คอมไพเลอร์ Java ใช้งานได้เวทมนตร์และที่ที่เราได้รับพารามิเตอร์อินเตอร์เฟสการทำงานเราสามารถส่งผ่านในการแสดงออกหรือการอ้างอิงวิธีการของแลมบ์ดา ตอนนี้เราสามารถเข้าสู่โลกแห่งการแสดงออกของแลมบ์ดาและห้องสมุด JDK ที่ปรับตัวให้พวกเขารู้สึกสนุกกับพวกเขา ในบทต่อไปเราจะเริ่มต้นด้วยการดำเนินการที่พบบ่อยที่สุดในการเขียนโปรแกรมและปลดปล่อยพลังของการแสดงออกของแลมบ์ดา