บทที่ 2: การใช้คอลเลกชัน
เรามักจะใช้คอลเลกชัน ตัวเลข สตริง และอ็อบเจ็กต์ต่างๆ มีทุกที่ และแม้ว่าโค้ดที่ดำเนินการคอลเลกชันจะสามารถปรับให้เหมาะสมได้เล็กน้อย แต่ก็จะทำให้โค้ดมีความชัดเจนมากขึ้น ในบทนี้ เราจะสำรวจวิธีใช้นิพจน์แลมบ์ดาเพื่อจัดการคอลเลกชัน เราใช้มันเพื่อสำรวจคอลเลกชัน แปลงคอลเลกชันเป็นคอลเลกชันใหม่ ลบองค์ประกอบออกจากคอลเลกชัน และรวมคอลเลกชัน
สำรวจรายการ
การข้ามรายการเป็นการดำเนินการชุดขั้นพื้นฐานที่สุด และการดำเนินการมีการเปลี่ยนแปลงบางอย่างตลอดหลายปีที่ผ่านมา เราใช้ตัวอย่างเล็กๆ น้อยๆ ของชื่อการข้าม โดยแนะนำตั้งแต่เวอร์ชันเก่าที่สุดไปจนถึงเวอร์ชันที่หรูหราที่สุดในปัจจุบัน
เราสามารถสร้างรายชื่อที่ไม่เปลี่ยนรูปได้อย่างง่ายดายด้วยรหัสต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
รายการสุดท้าย <String> เพื่อน =
Arrays.asList("ไบรอัน", "เนท", "นีล", "ราจู", "ซาร่า", "สกอตต์");
System.out.println(friends.get(i));
-
ต่อไปนี้เป็นวิธีการทั่วไปในการข้ามรายการและพิมพ์ แม้ว่าจะเป็นวิธีทั่วไปที่สุดก็ตาม:
คัดลอกรหัสรหัสดังต่อไปนี้:
สำหรับ (int i = 0; i < friends.size(); i++) {
System.out.println(friends.get(i));
-
ฉันเรียกวิธีนี้ว่าการเขียนแบบมาโซคิสม์ มีเนื้อหาละเอียดและผิดพลาดได้ง่าย เราต้องหยุดและคิดเกี่ยวกับมัน "มันคือ i< หรือ i<=?" สิ่งนี้สมเหตุสมผลเมื่อเราต้องดำเนินการกับองค์ประกอบเฉพาะ แต่ถึงอย่างนั้น เราก็ยังสามารถใช้นิพจน์การทำงานที่เป็นไปตามหลักการของ ความไม่เปลี่ยนรูปซึ่งเราจะหารือกันในไม่ช้า
Java ยังมีโครงสร้างที่ค่อนข้างสูงอีกด้วย
คัดลอกรหัสรหัสดังต่อไปนี้:
คอลเลกชัน/fpij/Iteration.java
สำหรับ (ชื่อสตริง : เพื่อน) {
System.out.println (ชื่อ);
-
ภายใต้ประทุน การวนซ้ำในลักษณะนี้ถูกนำมาใช้โดยใช้อินเทอร์เฟซ Iterator โดยเรียกใช้เมธอด hasNext และถัดไป ทั้งสองวิธีเป็นตัววนซ้ำภายนอก และจะรวมวิธีการดำเนินการกับสิ่งที่คุณต้องการทำ เราควบคุมการวนซ้ำอย่างชัดเจน โดยบอกว่าจะเริ่มต้นที่ไหนและสิ้นสุดที่ใด เวอร์ชันที่สองดำเนินการนี้ภายใต้ประทุนโดยใช้วิธี Iterator ภายใต้การดำเนินการที่ชัดเจน คุณยังสามารถใช้คำสั่งแบ่งและดำเนินการต่อเพื่อควบคุมการวนซ้ำได้ เวอร์ชันที่สองมีบางสิ่งที่ขาดหายไปจากเวอร์ชันแรก แนวทางนี้ดีกว่าวิธีแรกถ้าเราไม่ต้องการแก้ไของค์ประกอบของคอลเลกชัน อย่างไรก็ตาม ทั้งสองวิธีนี้มีความจำเป็นและควรละทิ้งไปใน Java ปัจจุบัน มีสาเหตุหลายประการในการเปลี่ยนรูปแบบการใช้งาน:
1. for loop นั้นเป็นอนุกรมและยากต่อการขนาน
2. การวนซ้ำดังกล่าวไม่ใช่แบบโพลีมอร์ฟิก สิ่งที่คุณได้รับคือสิ่งที่คุณขอ เราส่งต่อคอลเลกชันโดยตรงไปยัง for loop แทนที่จะเรียกเมธอด (ซึ่งรองรับความหลากหลาย) บนคอลเลกชันเพื่อดำเนินการเฉพาะ
3. จากมุมมองของการออกแบบ โค้ดที่เขียนในลักษณะนี้ละเมิดหลักการ "บอกอย่าถาม" เราขอให้ดำเนินการวนซ้ำแทนที่จะปล่อยให้การวนซ้ำไปที่ไลบรารีพื้นฐาน
ถึงเวลาเปลี่ยนจากการเขียนโปรแกรมที่จำเป็นแบบเก่าไปเป็นการเขียนโปรแกรมเชิงฟังก์ชันที่หรูหรายิ่งขึ้นของตัววนซ้ำภายใน หลังจากใช้ตัววนซ้ำภายใน เราจะปล่อยให้การดำเนินการเฉพาะหลายอย่างเป็นไลบรารีวิธีการพื้นฐานเพื่อดำเนินการ เพื่อให้คุณสามารถมุ่งเน้นไปที่ข้อกำหนดทางธุรกิจเฉพาะได้มากขึ้น ฟังก์ชันพื้นฐานจะต้องรับผิดชอบในการวนซ้ำ ขั้นแรกเราใช้ตัววนซ้ำภายในเพื่อระบุรายชื่อ
อินเทอร์เฟซ Iterable ได้รับการปรับปรุงใน JDK8 โดยมีชื่อพิเศษที่เรียกว่า forEach ซึ่งรับพารามิเตอร์ประเภท Comsumer ดังที่ชื่อกล่าวไว้ อินสแตนซ์ Consumer จะใช้ออบเจ็กต์ที่ส่งผ่านไปด้วยวิธีการยอมรับ เราใช้ไวยากรณ์คลาสภายในที่ไม่ระบุชื่อที่คุ้นเคยเพื่อใช้วิธี forEach:
คัดลอกรหัสรหัสดังต่อไปนี้:
friends.forEach (ผู้บริโภคใหม่ <สตริง> () { โมฆะสาธารณะยอมรับ (ชื่อสตริงสุดท้าย) {
System.out.println (ชื่อ); }
-
เราเรียกเมธอด forEach ในคอลเล็กชันของเพื่อน โดยส่งต่อเป็นการใช้งาน Consumer โดยไม่ระบุชื่อ เมธอด forEach นี้เรียกวิธีการยอมรับที่ส่งผ่านใน Consumer สำหรับแต่ละองค์ประกอบในคอลเลกชัน ทำให้สามารถประมวลผลองค์ประกอบนี้ได้ ในตัวอย่างนี้ เราเพียงพิมพ์ค่าของมัน ซึ่งก็คือชื่อ มาดูผลลัพธ์ของเวอร์ชันนี้ซึ่งเหมือนกับสองเวอร์ชันก่อนหน้า:
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน
เนท
นีล
ราจู
ซาร่า
สกอตต์
เราเปลี่ยนแปลงเพียงสิ่งเดียวเท่านั้น: เราทิ้งลูปที่ล้าสมัยแล้วและใช้ตัววนซ้ำภายในใหม่ ข้อดีคือเราไม่จำเป็นต้องระบุวิธีวนซ้ำคอลเลกชัน และสามารถมุ่งเน้นที่วิธีการประมวลผลแต่ละองค์ประกอบได้มากขึ้น ข้อเสียคือโค้ดดูละเอียดกว่า ซึ่งเกือบจะทำให้ความสนุกของรูปแบบการเขียนโค้ดใหม่หมดไป โชคดีที่สิ่งนี้เปลี่ยนแปลงได้ง่าย และนี่คือจุดที่พลังของนิพจน์แลมบ์ดาและคอมไพเลอร์ใหม่เข้ามามีบทบาท เรามาแก้ไขอีกครั้งหนึ่งและแทนที่คลาสภายในที่ไม่ระบุชื่อด้วยนิพจน์แลมบ์ดา
คัดลอกรหัสรหัสดังต่อไปนี้:
friends.forEach((ชื่อสตริงสุดท้าย) -> System.out.println(ชื่อ));
มันดูดีขึ้นมากด้วยวิธีนี้ มีโค้ดน้อยกว่า แต่ก่อนอื่นเรามาดูความหมายกันก่อน เมธอด forEach เป็นฟังก์ชันที่มีลำดับสูงกว่าที่ได้รับแลมบ์ดาเอ็กซ์เพรสชันหรือบล็อกโค้ดเพื่อดำเนินการกับองค์ประกอบในรายการ ในการเรียกแต่ละครั้ง องค์ประกอบในคอลเลกชันจะถูกผูกไว้กับตัวแปรชื่อ ไลบรารีพื้นฐานเป็นเจ้าภาพกิจกรรมการเรียกใช้นิพจน์แลมบ์ดา สามารถตัดสินใจชะลอการดำเนินการนิพจน์ และดำเนินการคำนวณแบบขนานได้ หากเหมาะสม ผลลัพธ์ของเวอร์ชันนี้ก็เหมือนกับเวอร์ชันก่อนหน้าเช่นกัน
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน
เนท
นีล
ราจู
ซาร่า
สกอตต์
เวอร์ชันตัววนซ้ำภายในมีความกระชับมากขึ้น ยิ่งไปกว่านั้น โดยการใช้มัน เราสามารถมุ่งเน้นไปที่การประมวลผลของแต่ละองค์ประกอบมากขึ้น แทนที่จะข้ามมันไป - นี่เป็นการประกาศ
อย่างไรก็ตามเวอร์ชันนี้มีข้อบกพร่อง เมื่อเมธอด forEach เริ่มดำเนินการ ซึ่งต่างจากสองเวอร์ชันอื่น เราไม่สามารถแยกออกจากการวนซ้ำนี้ได้ (แน่นอนว่ามีวิธีอื่นในการทำเช่นนี้) ดังนั้นวิธีการเขียนนี้จึงมักใช้กันมากขึ้นเมื่อต้องประมวลผลแต่ละองค์ประกอบในคอลเลกชัน ต่อไปเราจะแนะนำฟังก์ชันอื่นๆ ที่ช่วยให้เราสามารถควบคุมกระบวนการวนซ้ำได้
ไวยากรณ์มาตรฐานสำหรับนิพจน์แลมบ์ดาคือการใส่พารามิเตอร์ไว้ภายใน () ระบุข้อมูลประเภท และใช้เครื่องหมายจุลภาคเพื่อแยกพารามิเตอร์ เพื่อที่จะปลดปล่อยเรา คอมไพเลอร์ Java ยังสามารถดำเนินการหักประเภทได้โดยอัตโนมัติ แน่นอนว่าการไม่เขียนแบบจะสะดวกกว่า มีงานน้อยลง และโลกก็เงียบลง ต่อไปนี้เป็นเวอร์ชันก่อนหน้าหลังจากลบประเภทพารามิเตอร์แล้ว:
คัดลอกรหัสรหัสดังต่อไปนี้:
friends.forEach((ชื่อ) -> System.out.println(ชื่อ));
ในตัวอย่างนี้ คอมไพลเลอร์ Java รู้ว่าประเภทของชื่อคือ String ผ่านการวิเคราะห์บริบท จะดูที่ลายเซ็นของวิธีการที่เรียกว่า forEach แล้ววิเคราะห์อินเทอร์เฟซการทำงานในพารามิเตอร์ จากนั้นจะวิเคราะห์วิธีนามธรรมในอินเทอร์เฟซนี้ และตรวจสอบจำนวนและประเภทของพารามิเตอร์ แม้ว่านิพจน์แลมบ์ดานี้จะได้รับพารามิเตอร์หลายตัว เรายังคงสามารถดำเนินการหักประเภทได้ แต่ในกรณีนี้ พารามิเตอร์ทั้งหมดไม่สามารถมีประเภทพารามิเตอร์ได้ ในนิพจน์แลมบ์ดา จะต้องเขียนประเภทพารามิเตอร์เลย หรือหากเขียนไว้ก็ต้องเขียนไว้ อย่างเต็มที่
คอมไพลเลอร์ Java ปฏิบัติต่อนิพจน์แลมบ์ดาด้วยพารามิเตอร์ตัวเดียวโดยเฉพาะ: หากคุณต้องการทำการอนุมานประเภท คุณสามารถละเว้นวงเล็บรอบพารามิเตอร์ได้
คัดลอกรหัสรหัสดังต่อไปนี้:
friends.forEach (ชื่อ -> System.out.println (ชื่อ));
มีข้อแม้เล็กๆ น้อยๆ ที่นี่: พารามิเตอร์ที่ใช้สำหรับการอนุมานประเภทไม่ใช่ประเภทสุดท้าย ในตัวอย่างก่อนหน้าของการประกาศประเภทอย่างชัดเจน เรายังทำเครื่องหมายพารามิเตอร์ว่าเป็นขั้นสุดท้ายด้วย ซึ่งจะป้องกันไม่ให้คุณเปลี่ยนค่าของพารามิเตอร์ในนิพจน์แลมบ์ดา โดยทั่วไปแล้ว การปรับเปลี่ยนค่าของพารามิเตอร์ถือเป็นนิสัยที่ไม่ดี ซึ่งสามารถทำให้เกิด BUG ได้ง่าย ดังนั้นจึงเป็นนิสัยที่ดีที่จะทำเครื่องหมายว่าเป็นครั้งสุดท้าย น่าเสียดาย หากเราต้องการใช้การอนุมานประเภท เราต้องปฏิบัติตามกฎด้วยตนเอง และไม่แก้ไขพารามิเตอร์ เนื่องจากคอมไพเลอร์ไม่ได้ปกป้องเราอีกต่อไป
ต้องใช้ความพยายามอย่างมากในการมาถึงจุดนี้ แต่ตอนนี้จำนวนโค้ดก็น้อยลงเล็กน้อย แต่นี่ไม่ใช่วิธีที่ง่ายที่สุด มาลองใช้เวอร์ชันมินิมอลลิสต์ล่าสุดนี้กันดีกว่า
คัดลอกรหัสรหัสดังต่อไปนี้:
friends.forEach(System.out::println);
ในโค้ดข้างต้นเราใช้การอ้างอิงวิธีการ เราสามารถแทนที่โค้ดทั้งหมดด้วยชื่อวิธีการได้โดยตรง เราจะสำรวจเรื่องนี้แบบเจาะลึกในหัวข้อถัดไป แต่สำหรับตอนนี้ เราจะมานึกถึงคำพูดอันโด่งดังของ Antoine de Saint-Exupéry: ความสมบูรณ์แบบไม่ใช่สิ่งที่สามารถเพิ่มได้ แต่เป็นสิ่งที่ไม่สามารถพรากไปได้อีก
สำนวน Lambda ช่วยให้เราสามารถสำรวจคอลเลกชันต่างๆ ได้อย่างกระชับและชัดเจน ในหัวข้อถัดไป เราจะพูดถึงวิธีที่ช่วยให้เราสามารถเขียนโค้ดที่กระชับเมื่อดำเนินการลบและการแปลงคอลเลกชัน