เราได้ใช้เมธอด collect() หลายครั้งก่อนที่จะรวมองค์ประกอบที่ส่งคืนโดย Stream ลงใน ArrayList นี่คือการดำเนินการลดขนาด ซึ่งมีประโยชน์สำหรับการแปลงคอลเลกชันเป็นประเภทอื่น (โดยปกติแล้วเป็นคอลเลกชันที่ไม่แน่นอน) ฟังก์ชัน collect() หากใช้ร่วมกับวิธีการบางอย่างในคลาสเครื่องมือ Collectors สามารถให้ความสะดวกอย่างมาก ดังที่เราจะแนะนำในส่วนนี้
ลองใช้รายการบุคคลก่อนหน้าเป็นตัวอย่างต่อไปเพื่อดูว่าเมธอด collect() สามารถทำอะไรได้บ้าง สมมติว่าเราต้องการค้นหาทุกคนที่มีอายุมากกว่า 20 ปีจากรายการเดิม นี่คือเวอร์ชันที่ใช้งานโดยใช้ความไม่แน่นอนและเมธอด forEach():
คัดลอกรหัสรหัสดังต่อไปนี้:
รายการ<บุคคล> เก่ากว่า20 = ใหม่ ArrayList<>(); people.stream()
.filter(บุคคล -> person.getAge() > 20)
.forEach(คน -> เก่ากว่าThan20.add(คน)); System.out.println("ผู้ที่มีอายุมากกว่า 20: " + เก่ากว่า20);
เราใช้เมธอด filter() เพื่อกรองผู้ที่มีอายุมากกว่า 20 ปีออกจากรายการ จากนั้นในเมธอด forEach เราจะเพิ่มองค์ประกอบให้กับ ArrayList ที่ได้เตรียมใช้งานไว้ก่อนหน้านี้ มาดูผลลัพธ์ของโค้ดนี้ก่อน จากนั้นจึงสร้างใหม่ในภายหลัง
คัดลอกรหัสรหัสดังต่อไปนี้:
ผู้ที่มีอายุมากกว่า 20 ปี: [ซาร่า - 21, เจน - 21, เกร็ก - 35]
ผลลัพธ์ของโปรแกรมถูกต้อง แต่ยังมีปัญหาเล็กน้อย ประการแรก การเพิ่มองค์ประกอบให้กับคอลเลกชันเป็นการดำเนินการระดับต่ำ - มีความจำเป็น ไม่ใช่การประกาศ หากเราต้องการแปลงการวนซ้ำนี้ให้เป็นการทำงานพร้อมกัน เราต้องพิจารณาปัญหาความปลอดภัยของเธรด - ความแปรปรวนทำให้ยากต่อการขนาน โชคดีที่ปัญหานี้สามารถแก้ไขได้ง่ายโดยใช้เมธอด collect() เรามาดูกันว่าสิ่งนี้สำเร็จได้อย่างไร
วิธีการ collect() ยอมรับ Stream และรวบรวมไว้ในคอนเทนเนอร์ผลลัพธ์ เมื่อต้องการทำเช่นนี้ จะต้องรู้สามสิ่ง:
+ วิธีสร้างคอนเทนเนอร์ผลลัพธ์ (เช่น การใช้เมธอด ArrayList::new) + วิธีเพิ่มองค์ประกอบเดียวลงในคอนเทนเนอร์ (เช่น การใช้เมธอด ArrayList::add) + วิธีผสานชุดผลลัพธ์หนึ่งเข้ากับอีกชุดหนึ่ง (เช่น การใช้เมธอด ArrayList: :addAll)
รายการสุดท้ายไม่จำเป็นสำหรับการดำเนินการแบบอนุกรม รหัสได้รับการออกแบบมาเพื่อรองรับการทำงานทั้งแบบอนุกรมและแบบขนาน
เราจัดเตรียมการดำเนินการเหล่านี้ให้กับวิธีการรวบรวมและปล่อยให้มันรวบรวมสตรีมที่ถูกกรอง
คัดลอกรหัสรหัสดังต่อไปนี้:
รายการ <บุคคล> เก่ากว่า 20 =
คน.สตรีม()
.filter(บุคคล -> person.getAge() > 20)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("ผู้ที่มีอายุมากกว่า 20 ปี: " + อายุมากกว่า 20 ปี);
ผลลัพธ์ของโค้ดนี้ยังคงเหมือนเดิม แต่มีข้อดีหลายประการในการเขียนด้วยวิธีนี้
ประการแรก วิธีการเขียนโปรแกรมของเราเน้นและแสดงออกมากขึ้น โดยถ่ายทอดวัตถุประสงค์ของการรวบรวมผลลัพธ์ลงใน ArrayList อย่างชัดเจน พารามิเตอร์แรกของ collect() คือโรงงานหรือผู้ผลิต และพารามิเตอร์ต่อมาคือการดำเนินการที่ใช้ในการรวบรวมองค์ประกอบ
ประการที่สอง เนื่องจากเราไม่ได้ทำการแก้ไขโค้ดอย่างชัดเจน เราจึงสามารถดำเนินการวนซ้ำแบบคู่ขนานได้อย่างง่ายดาย เราปล่อยให้ไลบรารี่พื้นฐานจัดการการแก้ไข และมันจะดูแลปัญหาการประสานงานและความปลอดภัยของเธรด แม้ว่า ArrayList เองจะไม่ปลอดภัยสำหรับเธรดก็ตาม แต่ก็ทำได้ดีมาก
หากเงื่อนไขอนุญาต เมธอด collect() สามารถเพิ่มอิลิเมนต์ให้กับรายการย่อยที่แตกต่างกันแบบขนาน จากนั้นจึงรวมเข้าเป็นรายการขนาดใหญ่ด้วยวิธีที่ปลอดภัยสำหรับเธรด (พารามิเตอร์สุดท้ายใช้สำหรับการดำเนินการผสาน)
เราได้เห็นแล้วว่าการใช้เมธอด collect() มีประโยชน์มากมาย แทนที่จะเพิ่มองค์ประกอบลงในรายการด้วยตนเอง ลองดูเวอร์ชันที่โอเวอร์โหลดของวิธีนี้ - ง่ายกว่าและสะดวกกว่า - ใช้ Collector เป็นพารามิเตอร์ Collector นี้เป็นอินเทอร์เฟซที่มีผู้ผลิต adders และตัวรวม ในเวอร์ชันก่อนหน้านี้ การดำเนินการเหล่านี้ถูกส่งผ่านไปยังเมธอดแบบอิสระ การใช้ Collector นั้นง่ายกว่าและสามารถนำกลับมาใช้ใหม่ได้ คลาสเครื่องมือ Collectors จัดเตรียมเมธอด toList ที่สามารถสร้างการใช้งาน Collector เพื่อเพิ่มองค์ประกอบให้กับ ArrayList มาแก้ไขโค้ดก่อนหน้าแล้วใช้เมธอด collect()
คัดลอกรหัสรหัสดังต่อไปนี้:
รายการ <บุคคล> เก่ากว่า 20 =
คน.สตรีม()
.filter(บุคคล -> person.getAge() > 20)
.collect(Collectors.toList());
System.out.println("ผู้ที่มีอายุมากกว่า 20 ปี: " + อายุมากกว่า 20 ปี);
มีการใช้เวอร์ชันที่กระชับของเมธอด collect() ของคลาสเครื่องมือ Collectors แต่สามารถใช้ได้มากกว่าหนึ่งวิธี มีวิธีการที่แตกต่างกันหลายวิธีในคลาสเครื่องมือ Collectors เพื่อดำเนินการรวบรวมและดำเนินการเพิ่มที่แตกต่างกัน ตัวอย่างเช่น นอกเหนือจากเมธอด toList() แล้ว ยังมีเมธอด toSet() ซึ่งสามารถเพิ่มให้กับ Set ได้ เมธอด toMap() ซึ่งสามารถใช้เพื่อรวบรวมเป็นชุดคีย์-ค่า และ joining() วิธีการซึ่งสามารถต่อเป็นสตริงได้ นอกจากนี้เรายังสามารถรวมวิธีการต่าง ๆ เช่น mapping(), collectingAndThen(), minBy(), maxBy() และ groupingBy() เพื่อใช้งาน
ลองใช้เมธอด groupingBy() เพื่อจัดกลุ่มคนตามอายุ
คัดลอกรหัสรหัสดังต่อไปนี้:
แผนที่<จำนวนเต็ม รายการ<บุคคล>> peopleByAge =
คน.สตรีม()
.collect(Collectors.groupingBy(Person::getAge));
System.out.println("จัดกลุ่มตามอายุ: " + peopleByAge);
เพียงเรียกใช้เมธอด collect() เพื่อจัดกลุ่มให้เสร็จสมบูรณ์ groupingBy() ยอมรับนิพจน์แลมบ์ดาหรือการอ้างอิงเมธอด ซึ่งเรียกว่าฟังก์ชันการจำแนกประเภท และจะส่งกลับค่าของคุณลักษณะเฉพาะของวัตถุที่ต้องการจัดกลุ่ม ตามค่าที่ส่งคืนโดยฟังก์ชันของเรา องค์ประกอบในบริบทการโทรจะถูกวางไว้ในกลุ่มใดกลุ่มหนึ่ง ผลลัพธ์ของการจัดกลุ่มสามารถดูได้ในผลลัพธ์:
คัดลอกรหัสรหัสดังต่อไปนี้:
จัดกลุ่มตามอายุ: {35=[Greg - 35], 20=[John - 20], 21=[Sara - 21, Jane - 21]}
ผู้คนถูกจัดกลุ่มตามอายุ
ในตัวอย่างก่อนหน้านี้ เราจัดกลุ่มผู้คนตามอายุ ตัวแปรของเมธอด groupingBy() สามารถจัดกลุ่มได้หลายเงื่อนไข groupingBy() วิธีการอย่างง่ายใช้ลักษณนามเพื่อรวบรวมองค์ประกอบ ตัวรวบรวม groupingBy() ทั่วไปสามารถระบุตัวรวบรวมสำหรับแต่ละกลุ่มได้ กล่าวอีกนัยหนึ่ง องค์ประกอบต่างๆ จะต้องผ่านตัวแยกประเภทและคอลเลกชันต่างๆ ในระหว่างกระบวนการรวบรวม ดังที่เราจะเห็นด้านล่าง
ดำเนินการต่อด้วยตัวอย่างข้างต้น คราวนี้แทนที่จะจัดกลุ่มตามอายุ เราเพียงแต่ได้รับชื่อของบุคคลและจัดเรียงตามอายุของพวกเขา
คัดลอกรหัสรหัสดังต่อไปนี้:
แมป <จำนวนเต็ม รายการ <สตริง>> nameOfPeopleByAge =
คน.สตรีม()
.เก็บรวบรวม(
groupingBy(บุคคล::getAge, การทำแผนที่ (บุคคล::getName, toList())));
System.out.println("บุคคลที่จัดกลุ่มตามอายุ: " + nameOfPeopleByAge);
groupingBy() เวอร์ชันนี้ยอมรับพารามิเตอร์สองตัว ตัวแรกคืออายุ ซึ่งเป็นเงื่อนไขสำหรับการจัดกลุ่ม และตัวที่สองคือตัวรวบรวม ซึ่งเป็นผลลัพธ์ที่ส่งคืนโดยฟังก์ชัน mapping() วิธีการเหล่านี้ทั้งหมดมาจากคลาสเครื่องมือ Collectors และนำเข้าแบบคงที่ในโค้ดนี้ เมธอด mapping() ยอมรับพารามิเตอร์สองตัว ตัวหนึ่งคือแอตทริบิวต์ที่ใช้สำหรับการทำแผนที่ และอีกตัวคือตำแหน่งที่จะรวบรวมออบเจ็กต์ เช่น รายการหรือชุด มาดูผลลัพธ์ของโค้ดด้านบนกัน:
คัดลอกรหัสรหัสดังต่อไปนี้:
ผู้คนที่จัดกลุ่มตามอายุ: {35=[Greg], 20=[John], 21=[Sara, Jane]}
อย่างที่คุณเห็น ชื่อของผู้คนถูกจัดกลุ่มตามอายุ
มาดูการดำเนินการรวมกันอีกครั้ง: จัดกลุ่มตามตัวอักษรตัวแรกของชื่อ จากนั้นเลือกบุคคลที่อายุมากที่สุดในแต่ละกลุ่ม
คัดลอกรหัสรหัสดังต่อไปนี้:
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
แผนที่<อักขระ ตัวเลือก<บุคคล>> เก่าที่สุดPersonOfEachLetter =
คน.สตรีม()
.collect(groupingBy(บุคคล -> person.getName().charAt(0),
ลด (BinaryOperator.maxBy (byAge))));
System.out.println("บุคคลที่เก่าแก่ที่สุดของแต่ละตัวอักษร:");
System.out.println (oldestPersonOfEachLetter);
ขั้นแรกเราเรียงลำดับชื่อตามตัวอักษร เพื่อให้บรรลุเป้าหมายนี้ เราส่งนิพจน์แลมบ์ดาเป็นพารามิเตอร์แรกของ groupingBy() นิพจน์แลมบ์ดานี้ใช้เพื่อส่งคืนอักษรตัวแรกของชื่อสำหรับการจัดกลุ่ม พารามิเตอร์ตัวที่สองไม่มีการแมป () อีกต่อไป แต่ดำเนินการลดขนาด ภายในแต่ละกลุ่มจะใช้เมธอด maxBy() เพื่อรับองค์ประกอบที่เก่าที่สุดจากองค์ประกอบทั้งหมด ไวยากรณ์ดูป่องเล็กน้อยเนื่องจากมีการดำเนินการหลายอย่างที่รวมเข้าด้วยกัน แต่ทั้งหมดอ่านได้ดังนี้: จัดกลุ่มตามอักษรตัวแรกของชื่อ จากนั้นจึงทำงานจนถึงลำดับที่อาวุโสที่สุดในกลุ่ม พิจารณาผลลัพธ์ของโค้ดนี้ ซึ่งแสดงรายการบุคคลที่อายุมากที่สุดในกลุ่มชื่อที่ขึ้นต้นด้วยตัวอักษรที่กำหนด
คัดลอกรหัสรหัสดังต่อไปนี้:
บุคคลที่อายุมากที่สุดในแต่ละจดหมาย:
{S=ทางเลือก[ซาร่า - 21], G=ทางเลือก[เกร็ก - 35], J=ทางเลือก[เจน - 21]}
เราได้สัมผัสถึงพลังของเมธอด collect() และคลาสยูทิลิตี้ Collectors แล้ว ในเอกสารอย่างเป็นทางการของ IDE หรือ JDK ของคุณ ใช้เวลาศึกษาคลาสเครื่องมือ Collectors และทำความคุ้นเคยกับวิธีการต่างๆ ที่มีให้ ต่อไปเราจะใช้นิพจน์แลมบ์ดาเพื่อใช้ตัวกรองบางตัว