Java Collectors.filtering: กรองสมาชิกแต่ยังเก็บกลุ่มที่ผลลัพธ์เป็นศูนย์
คำถามว่า “แต่ละทีมมีสมาชิก active กี่คน” ดูเหมือน stream().filter(...).collect(groupingBy(...)) ก็พอ แต่ลำดับนั้นลบสมาชิก inactive ก่อนสร้างกลุ่ม ทีมที่ไม่มีใคร active จึงหายจาก map ทั้ง key Collectors.filtering วาง predicate เป็น downstream collector หลัง groupingBy ทำให้ key ถูกสร้างจาก input ทุกคน แล้วนับเฉพาะคนที่ผ่านเงื่อนไขครับ
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
record Member(String team, boolean active) {}
static Map<String, Long> activeByTeam(List<Member> members) {
return members.stream().collect(Collectors.groupingBy(
Member::team,
Collectors.filtering(Member::active, Collectors.counting())
));
}
public static void main(String[] args) {
var members = List.of(
new Member("green", true),
new Member("green", false),
new Member("amber", false)
);
var result = activeByTeam(members);
assert result.equals(Map.of("green", 1L, "amber", 0L)) : result;
assert result.containsKey("amber");
System.out.println(result);
}
}
เลือกตำแหน่ง filter จากความหมาย
ถ้ารายงานต้องแสดงเฉพาะกลุ่มที่มีสมาชิกผ่านเงื่อนไข การ filter upstream อ่านง่ายกว่าและลดงานก่อน grouping แต่ถ้าศูนย์เป็นคำตอบที่มีความหมาย เช่นทีม amber ต้องแสดง 0 เพื่อให้ dashboard รู้ว่าทีมมีอยู่จริง downstream filtering คือสัญญาที่ถูกต้อง ความต่างไม่ได้เป็นแค่ optimization แต่เปลี่ยน shape ของผลลัพธ์
filtering ไม่ได้สร้าง key ที่ไม่มีอยู่ใน input เลย หากต้องการทุกทีมจาก master data แม้วันนี้ไม่มี member ต้องเริ่มจากชุดทีมที่รู้จักแล้ว merge count หรือ prefill map เอง อย่าสรุปว่า downstream collector จะเติม dimension ที่ไม่เคยปรากฏ
สำหรับ parallel stream ให้ downstream collector และฟังก์ชัน classifier ไม่มี mutable shared state groupingByConcurrent เปลี่ยนทั้งชนิด map และเงื่อนไข concurrency จึงควรเลือกหลังวัดจริง ไม่ใช่สลับเพราะเห็นข้อมูลเยอะ นอกจากนี้ predicate ที่ทำ I/O จะทำให้ pipeline ประเมินยาก ควรเตรียมข้อมูลก่อนหรือย้ายไป service boundary
Recap
- filter upstream ทำให้กลุ่มที่ไม่มีสมาชิกผ่านเงื่อนไขหายไป
Collectors.filteringdownstream รักษา key และให้ค่า count ศูนย์- มันไม่สร้างกลุ่มที่ไม่ปรากฏใน input
- เลือกตามความหมายของ empty group ก่อนเรื่องความสั้นครับ