นากกายต้นฉบับเปิดกล่องย่อยจากแต่ละโครงการแล้วเทลูกแก้วลงถาดรวมตามทีมโดยไม่เหลือกล่องซ้อน

Java Collectors.flatMapping: รวมสมาชิกย่อยต่อกลุ่มโดยไม่เก็บ List ซ้อน

พี่โชว์Engineering

เมื่อ object หนึ่งมี collection ย่อย เช่นหนึ่ง project มีหลาย label และเราต้องการ Map<Team, Set<Label>> การ groupingBy ธรรมดามักจบที่ Map<Team, List<List<Label>>> แล้วต้อง flatten รอบสอง Collectors.flatMapping ให้ mapper คืน stream ย่อยและส่งสมาชิกทุกตัวเข้า downstream collector ของแต่ละกลุ่มโดยตรงครับ

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class Main {
    record Project(String team, List<String> labels) {}

    static Map<String, Set<String>> labelsByTeam(List<Project> projects) {
        return projects.stream().collect(Collectors.groupingBy(
                Project::team,
                Collectors.flatMapping(
                        project -> project.labels().stream(),
                        Collectors.toSet()
                )
        ));
    }

    public static void main(String[] args) {
        var projects = List.of(
                new Project("green", List.of("api", "urgent")),
                new Project("green", List.of("api", "batch")),
                new Project("amber", List.of())
        );
        var result = labelsByTeam(projects);
        assert result.get("green").equals(Set.of("api", "urgent", "batch"));
        assert result.get("amber").isEmpty();
        System.out.println(result);
    }
}

ทรัพยากรและข้อมูลว่าง

API จะปิด stream ที่ mapper คืนให้หลังนำสมาชิกไป downstream แล้ว แต่ mapper ควรคืน stream ใหม่ต่อ input และไม่ควร reuse stream เดิมเพราะ stream ใช้ได้ครั้งเดียว หากข้อมูลย่อยเป็น collection ใน memory การเรียก .stream() ตรง ๆ ชัดที่สุด ถ้ามาจาก I/O ควรจัด lifecycle นอก collector เพราะ exception และการปิด resource กลาง parallel reduction เข้าใจยาก

ตัวอย่างให้ list ว่างแทน null ทำให้ empty group ยังได้ set ว่าง ถ้า domain อนุญาต null ให้ normalize ก่อนเข้า pipeline หรือคืน Stream.empty() อย่าง explicit อย่าเขียน project.labels().stream() แล้วหวังว่า collector จะกลืน NullPointerException ให้

toSet() ไม่รับประกัน iteration order หาก output ต้องเสถียรสำหรับ JSON snapshot หรือ UI ให้ส่ง factory ของ collection ที่ต้องการผ่าน toCollection และบันทึกสัญญานั้นไว้ การ flatten ยังอาจขยายข้อมูลมาก ถ้าต้องจำกัดจำนวน tag ต่อทีมควรใช้ collector เฉพาะทางหรือประมวลผลแบบ imperative ที่เห็น limit ชัดกว่า

Recap

  • ใช้ flatMapping เมื่อหนึ่ง input ให้สมาชิกย่อยหลายตัวแก่ collector ของกลุ่ม
  • คืน stream ใหม่ทุกครั้งและรักษา resource lifecycle ให้ชัด
  • ใช้ empty collection แทน null เมื่อ domain อนุญาต
  • ระบุ order และขอบเขตขนาดผลลัพธ์เมื่อผู้ใช้พึ่งพาเรื่องเหล่านี้ครับ