คำอธิบายประกอบการแปล: แผนที่ (การทำแผนที่) และการลด (ลดความซับซ้อน) เป็นแนวคิดพื้นฐานสองประการในวิชาคณิตศาสตร์ พวกเขาปรากฏในภาษาการเขียนโปรแกรมเชิงฟังก์ชันต่างๆ มาเป็นเวลานาน จนกระทั่งปี 2003 Google ได้ดำเนินการเหล่านี้ต่อไป นำไปประยุกต์ใช้กับ หลังจากมีการใช้การประมวลผลแบบขนานในระบบแบบกระจาย ชื่อของชุดค่าผสมนี้เริ่มเปล่งประกายในโลกคอมพิวเตอร์ (พัดลมทำงานเหล่านั้นอาจคิดอย่างนั้น) ในบทความนี้ เราจะเห็นการเปิดตัวของแผนที่และลดการผสมผสานหลังจากที่ Java 8 รองรับการเขียนโปรแกรมเชิงฟังก์ชัน (นี่เป็นเพียงการแนะนำเบื้องต้นเท่านั้น โดยจะมีหัวข้อพิเศษในภายหลัง)
ลดชุด
จนถึงตอนนี้ เราได้แนะนำเทคนิคใหม่ๆ มากมายสำหรับการดำเนินการคอลเลกชัน: การค้นหาองค์ประกอบที่ตรงกัน การค้นหาองค์ประกอบแต่ละรายการ และการแปลงคอลเลกชัน การดำเนินการเหล่านี้มีสิ่งหนึ่งที่เหมือนกัน คือทั้งหมดดำเนินการบนองค์ประกอบเดียวในคอลเลกชัน ไม่จำเป็นต้องเปรียบเทียบองค์ประกอบหรือดำเนินการกับสององค์ประกอบ ในส่วนนี้ เราจะดูวิธีการเปรียบเทียบองค์ประกอบและรักษาผลลัพธ์การดำเนินการแบบไดนามิกระหว่างการข้ามผ่านคอลเลกชัน
เรามาเริ่มด้วยตัวอย่างง่ายๆ แล้วค่อยๆ พัฒนาต่อไป ในตัวอย่างแรก ขั้นแรกเราจะวนซ้ำคอลเลกชั่นเพื่อนและคำนวณจำนวนอักขระทั้งหมดในชื่อทั้งหมด
คัดลอกรหัสรหัสดังต่อไปนี้:
System.out.println("จำนวนตัวอักษรทั้งหมดในทุกชื่อ: " + friends.stream()
.mapToInt(ชื่อ -> name.length())
.ผลรวม());
ในการคำนวณจำนวนอักขระทั้งหมด เราจำเป็นต้องทราบความยาวของแต่ละชื่อ ซึ่งสามารถทำได้ง่ายๆ ด้วยเมธอด mapToInt() หลังจากที่เราเปลี่ยนชื่อให้มีความยาวที่สอดคล้องกันแล้ว เราเพียงแต่ต้องรวมเข้าด้วยกันในตอนท้ายเท่านั้น เรามีเมธอด sum() ในตัวเพื่อทำสิ่งนี้ให้สำเร็จ นี่คือผลลัพธ์สุดท้าย:
คัดลอกรหัสรหัสดังต่อไปนี้:
จำนวนตัวอักษรทั้งหมดในทุกชื่อ: 26
เราใช้การดำเนินการแผนที่รูปแบบหนึ่ง นั่นคือเมธอด mapToInt() (เช่น mapToInt, mapToDouble ฯลฯ ซึ่งจะสร้างสตรีมประเภทเฉพาะ เช่น IntStream, DoubleStream) จากนั้นคำนวณจำนวนอักขระทั้งหมดตาม ความยาวที่ส่งคืน
นอกจากการใช้วิธีรวมแล้ว ยังมีวิธีการที่คล้ายกันอีกมากมายที่สามารถใช้ได้ เช่น max() เพื่อค้นหาความยาวสูงสุด, min() เพื่อค้นหาความยาวขั้นต่ำ, sorted() เพื่อเรียงลำดับความยาว, ค่าเฉลี่ย() ถึง หาความยาวเฉลี่ย ฯลฯ รอก่อน
อีกแง่มุมที่น่าสนใจของตัวอย่างข้างต้นคือโหมด MapReduce ที่ได้รับความนิยมมากขึ้นเรื่อยๆ เมธอด map() ทำการแมป และเมธอด sum() เป็นการดำเนินการลดขนาดที่ใช้กันทั่วไป ในความเป็นจริง การใช้วิธี sum() ใน JDK ใช้วิธีการลด () ลองมาดูรูปแบบการดำเนินการลดที่ใช้กันทั่วไปบ้าง
ตัวอย่างเช่น เราวนซ้ำชื่อทั้งหมดและพิมพ์ชื่อที่ยาวที่สุด หากมีชื่อที่ยาวที่สุดหลายชื่อ เราจะพิมพ์ชื่อที่พบก่อน วิธีหนึ่งคือเราคำนวณความยาวสูงสุดแล้วเลือกองค์ประกอบแรกที่ตรงกับความยาวนี้ อย่างไรก็ตาม การทำเช่นนี้จำเป็นต้องข้ามผ่านรายการสองครั้ง ซึ่งไม่มีประสิทธิภาพเกินไป นี่คือจุดที่การดำเนินการลดขนาดเข้ามามีบทบาท
เราสามารถใช้การดำเนินการลดเพื่อเปรียบเทียบความยาวขององค์ประกอบทั้งสอง จากนั้นส่งคืนองค์ประกอบที่ยาวที่สุด และเปรียบเทียบกับองค์ประกอบที่เหลือเพิ่มเติม เช่นเดียวกับฟังก์ชันที่มีลำดับสูงกว่าอื่นๆ ที่เราเห็นก่อนหน้านี้ เมธอดลด () จะลัดเลาะไปตามคอลเลกชันทั้งหมดด้วย เหนือสิ่งอื่นใด ระบบจะบันทึกผลการคำนวณที่ส่งคืนโดยนิพจน์แลมบ์ดา หากมีตัวอย่างที่สามารถช่วยให้เราเข้าใจสิ่งนี้ได้ดีขึ้น เรามาลองดูโค้ดกันก่อน
คัดลอกรหัสรหัสดังต่อไปนี้:
ตัวเลือกสุดท้าย aLongName = friends.stream()
.reduce((name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(ชื่อ ->
System.out.println(String.format("ชื่อที่ยาวที่สุด: %s", ชื่อ)));
นิพจน์แลมบ์ดาที่ส่งผ่านไปยังเมธอดลด () จะได้รับพารามิเตอร์สองตัวคือ name1 และ name2 และจะเปรียบเทียบความยาวและส่งกลับค่าที่ยาวที่สุด วิธีลด () ไม่รู้ว่าเรากำลังจะทำอะไร ตรรกะนี้ถูกแยกออกไปในนิพจน์แลมบ์ดาที่เราส่งเข้าไป - นี่เป็นการนำรูปแบบกลยุทธ์ไปใช้เพียงเล็กน้อย
นิพจน์แลมบ์ดานี้สามารถปรับให้เข้ากับวิธีการนำไปใช้ของอินเทอร์เฟซการทำงานของ BinaryOperator ใน JDK นี่เป็นประเภทของอาร์กิวเมนต์ที่วิธีการลดยอมรับทุกประการ ลองใช้วิธีลดนี้และดูว่าสามารถเลือกชื่อที่ยาวที่สุดตัวแรกจากสองชื่อได้อย่างถูกต้องหรือไม่
คัดลอกรหัสรหัสดังต่อไปนี้:
ชื่อที่ยาวที่สุด: ไบรอัน
เมื่อเมธอดลด () ลัดเลาะไปตามคอลเลกชัน จะเรียกนิพจน์แลมบ์ดากับสององค์ประกอบแรกของคอลเลกชันก่อน และผลลัพธ์ที่ส่งคืนจากการเรียกจะยังคงใช้สำหรับการเรียกครั้งถัดไป ในการเรียกครั้งที่สอง ค่าของ name1 จะถูกผูกไว้กับผลลัพธ์ของการโทรครั้งก่อน และค่าของ name2 คือองค์ประกอบที่สามของคอลเลกชัน องค์ประกอบที่เหลือจะถูกเรียกตามลำดับนี้เช่นกัน ผลลัพธ์ของการเรียกนิพจน์แลมบ์ดาครั้งสุดท้ายคือผลลัพธ์ที่ส่งคืนโดยเมธอดลด () ทั้งหมด
ลด () วิธีการส่งกลับค่าตัวเลือกเนื่องจากคอลเลกชันที่ส่งผ่านไปอาจจะว่างเปล่า ในกรณีนี้จะไม่มีชื่อที่ยาวที่สุด หากรายการมีเพียงองค์ประกอบเดียว วิธีการลดจะส่งกลับองค์ประกอบนั้นโดยตรงและจะไม่เรียกนิพจน์แลมบ์ดา
จากตัวอย่างนี้ เราสามารถอนุมานได้ว่าผลลัพธ์ของการลดสามารถมีได้เพียงองค์ประกอบเดียวในชุดเท่านั้น หากเราต้องการส่งคืนค่าเริ่มต้นหรือค่าฐาน เราสามารถใช้ตัวแปรของเมธอดลด () ที่ยอมรับพารามิเตอร์เพิ่มเติมได้ ตัวอย่างเช่น หากชื่อที่สั้นที่สุดคือ Steve เราสามารถส่งต่อไปยังเมธอดลด () ได้ดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
สตริงสุดท้าย steveOrLonger = friends.stream()
.reduce("สตีฟ", (name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
หากมีชื่อที่ยาวกว่านั้น ชื่อนี้จะถูกเลือก มิฉะนั้น ค่าฐานของ Steve จะถูกส่งกลับ เมธอดลด () เวอร์ชันนี้จะไม่ส่งคืนออบเจ็กต์เสริม เนื่องจากหากคอลเลกชั่นว่างเปล่า ระบบจะส่งคืนค่าเริ่มต้นโดยไม่คำนึงถึงกรณีที่ไม่มีค่าส่งคืน
ก่อนที่เราจะจบบทนี้ เรามาดูการดำเนินการพื้นฐานแต่ไม่ง่ายนักในการดำเนินการเซต: การรวมองค์ประกอบต่างๆ
ผสานองค์ประกอบ
เราได้เรียนรู้วิธีการค้นหาองค์ประกอบ สำรวจ และแปลงคอลเลกชัน อย่างไรก็ตาม มีการดำเนินการทั่วไปอีกอย่างหนึ่ง นั่นคือการรวมองค์ประกอบคอลเลกชันเข้าด้วยกัน หากไม่มีฟังก์ชัน join() ที่เพิ่มเข้ามาใหม่ โค้ดที่กระชับและสง่างามที่กล่าวถึงก่อนหน้านี้คงไร้ผล วิธีการง่ายๆ นี้ใช้งานได้จริงจนได้กลายเป็นหนึ่งในฟังก์ชันที่ใช้บ่อยที่สุดใน JDK มาดูวิธีใช้พิมพ์องค์ประกอบในรายการโดยคั่นด้วยเครื่องหมายจุลภาค
เรายังคงใช้รายชื่อเพื่อนนี้ หากคุณใช้วิธีการเก่าในไลบรารี JDK คุณควรทำอย่างไรถ้าคุณต้องการพิมพ์ชื่อทั้งหมดโดยคั่นด้วยเครื่องหมายจุลภาค
เราต้องวนซ้ำรายการและพิมพ์องค์ประกอบทีละรายการ for loop ใน Java 5 ได้รับการปรับปรุงให้ดีขึ้นกว่ารุ่นก่อนหน้า ดังนั้นเรามาใช้งานกัน
คัดลอกรหัสรหัสดังต่อไปนี้:
สำหรับ (ชื่อสตริง : เพื่อน) {
System.out.print(ชื่อ + ", ");
-
System.out.println();
โค้ดนั้นง่ายมาก มาดูกันว่าผลลัพธ์ของมันคืออะไร
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน, เนท, นีล, ราจู, ซารา, สก็อตต์,
ให้ตายเถอะ มีเครื่องหมายจุลภาคที่น่ารำคาญในตอนท้าย (เราจะตำหนิสก็อตต์ในตอนท้ายได้ไหม) ฉันจะบอก Java ได้อย่างไรว่าอย่าใส่ลูกน้ำที่นี่ น่าเสียดายที่การวนซ้ำดำเนินการทีละขั้นตอน และไม่ใช่เรื่องง่ายที่จะทำสิ่งพิเศษในตอนท้าย เพื่อแก้ไขปัญหานี้ เราสามารถใช้วิธีวนซ้ำแบบเดิมได้
คัดลอกรหัสรหัสดังต่อไปนี้:
สำหรับ (int i = 0; i < friends.size() - 1; i++) {
System.out.print(friends.get(i) + `, ");
-
ถ้า(friends.size() > 0)
System.out.println(friends.get(friends.size() - 1));
มาดูกันว่าเอาท์พุตของเวอร์ชันนี้จะโอเคหรือไม่
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน, เนท, นีล, ราจู, ซารา, สก็อตต์
ผลลัพธ์ยังคงดีอยู่ แต่โค้ดนี้ไม่ประจบประแจง ช่วยเราด้วยจาวา
เราไม่ต้องทนกับความเจ็บปวดนี้อีกต่อไป คลาส StringJoiner ใน Java 8 ช่วยเราแก้ปัญหาเหล่านี้ ไม่เพียงเท่านั้น คลาส String ยังเพิ่มวิธีการรวมเพื่อให้เราสามารถแทนที่สิ่งข้างต้นด้วยโค้ดหนึ่งบรรทัด
คัดลอกรหัสรหัสดังต่อไปนี้:
System.out.println(String.join(", ", เพื่อน));
มาดูผลลัพธ์ก็น่าพอใจเหมือนโค้ดเลย
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน, เนท, นีล, ราจู, ซารา, สก็อตต์
ผลลัพธ์ยังคงดีอยู่ แต่โค้ดนี้ไม่ประจบประแจง ช่วยเราด้วยจาวา
เราไม่ต้องทนกับความเจ็บปวดนี้อีกต่อไป คลาส StringJoiner ใน Java 8 ช่วยเราแก้ปัญหาเหล่านี้ ไม่เพียงเท่านั้น คลาส String ยังเพิ่มวิธีการรวมเพื่อให้เราสามารถแทนที่สิ่งข้างต้นด้วยโค้ดหนึ่งบรรทัด
คัดลอกรหัสรหัสดังต่อไปนี้:
System.out.println(String.join(", ", เพื่อน));
มาดูผลลัพธ์ก็น่าพอใจเหมือนโค้ดเลย
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน, เนท, นีล, ราจู, ซารา, สก็อตต์
ในการใช้งานพื้นฐาน เมธอด String.join() เรียกคลาส StringJoiner เพื่อแยกค่าที่ส่งผ่านเป็นพารามิเตอร์ตัวที่สอง (ซึ่งเป็นพารามิเตอร์ที่มีความยาวผันแปรได้) ให้เป็นสตริงแบบยาว โดยใช้พารามิเตอร์ตัวแรกเป็นตัวคั่น แน่นอนว่าวิธีนี้เป็นมากกว่าแค่การใช้เครื่องหมายจุลภาคประกบกัน ตัวอย่างเช่น เราสามารถส่งผ่านเป็นพวงของเส้นทางและสะกด classpath ได้อย่างง่ายดาย ต้องขอบคุณเมธอดและคลาสที่เพิ่มใหม่เหล่านี้
เรารู้วิธีการเชื่อมต่อองค์ประกอบรายการแล้ว ก่อนที่จะเชื่อมต่อรายการ เรายังสามารถเปลี่ยนองค์ประกอบต่างๆ ได้ด้วย ต่อไป เรายังสามารถใช้เมธอด filter() เพื่อกรององค์ประกอบที่เราต้องการได้ ขั้นตอนสุดท้ายของการเชื่อมต่อองค์ประกอบรายการ โดยใช้เครื่องหมายจุลภาคหรือตัวคั่นอื่นๆ เป็นเพียงการดำเนินการลดแบบง่ายๆ
เราสามารถใช้เมธอดลด () เพื่อเชื่อมองค์ประกอบต่างๆ เข้าด้วยกันให้เป็นสตริงได้ แต่เราต้องดำเนินการบางอย่างในส่วนของเรา JDK มีวิธีการ collect() ที่สะดวกมาก ซึ่งเป็นอีกรูปแบบหนึ่งของการลด () เราสามารถใช้มันเพื่อรวมองค์ประกอบต่างๆ ให้เป็นค่าที่ต้องการได้
เมธอด collect() ดำเนินการลด แต่จะมอบหมายการดำเนินการเฉพาะให้กับตัวรวบรวมเพื่อดำเนินการ เราสามารถรวมองค์ประกอบที่แปลงแล้วเข้ากับ ArrayList ได้ จากตัวอย่างก่อนหน้านี้ เราสามารถเชื่อมองค์ประกอบที่แปลงแล้วเข้ากับสตริงที่คั่นด้วยเครื่องหมายจุลภาคได้
คัดลอกรหัสรหัสดังต่อไปนี้:
System.out.println(
เพื่อน.สตรีม()
.map(String::toUpperCase)
.collect(เข้าร่วม(", ")));
เราเรียกเมธอด collect() ในรายการที่แปลงแล้ว โดยส่งผ่านตัวรวบรวมที่ส่งคืนโดยเมธอด joining() เป็นเมธอดแบบสแตติกในคลาสเครื่องมือ Collectors ตัวรวบรวมเป็นเหมือนตัวรับ โดยรับอ็อบเจ็กต์ที่ส่งผ่านและจัดเก็บในรูปแบบที่คุณต้องการ: ArrayList, String ฯลฯ เราจะสำรวจวิธีการนี้เพิ่มเติมในวิธีการรวบรวมและคลาส Collector ในหน้า 52
นี่คือชื่อเอาต์พุต ตอนนี้จะเป็นตัวพิมพ์ใหญ่และคั่นด้วยเครื่องหมายจุลภาค
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบรอัน, เนท, นีล, ราจู, ซารา, สก็อตต์
สรุป
คอลเลกชั่นเป็นเรื่องธรรมดามากในการเขียนโปรแกรม ด้วย lambda expression การดำเนินการคอลเลกชั่นของ Java ง่ายขึ้นและง่ายขึ้น โค้ดเก่าๆ ที่ดูเทอะทะทั้งหมดสำหรับการดำเนินการรวบรวมสามารถแทนที่ได้ด้วยวิธีใหม่ที่สวยงามและกระชับนี้ ตัววนซ้ำภายในทำให้การข้ามผ่านคอลเลกชันและการเปลี่ยนแปลงสะดวกยิ่งขึ้น ห่างไกลจากปัญหาความแปรปรวน และการค้นหาองค์ประกอบการรวบรวมกลายเป็นเรื่องง่ายมาก คุณสามารถเขียนโค้ดได้น้อยลงมากโดยใช้วิธีการใหม่เหล่านี้ สิ่งนี้ทำให้โค้ดดูแลรักษาง่ายขึ้น เน้นไปที่ตรรกะทางธุรกิจมากขึ้น และดำเนินการพื้นฐานในการเขียนโปรแกรมน้อยลง
ในบทถัดไป เราจะดูว่านิพจน์แลมบ์ดาทำให้การดำเนินการพื้นฐานอื่นๆ ในการพัฒนาโปรแกรมง่ายขึ้นได้อย่างไร: การจัดการสตริงและการเปรียบเทียบวัตถุ