นากสีมิ้นต์ต้นฉบับกำลังแยกเมล็ดจากรางเดียวเข้าสองตะกร้าแล้วรวมผลบนโต๊ะสว่าง

Java Collectors.teeing: สรุปสองค่าใน stream pass เดียว

พี่โชว์Engineering

Java Stream ใช้ได้ครั้งเดียวครับ หากต้องการทั้ง count และ sum หลายทีมจึงสร้าง list กลางหรือเปิด stream สองรอบ Collectors.teeing ส่งทุก element เข้า downstream collectors สองตัว แล้วใช้ merger รวมผลสุดท้าย ทำให้ contract ว่าอ่าน source รอบเดียวชัดเจน

WHY: รวม reduction ที่สัมพันธ์กัน

teeing เหมือนวางกรวยสองอันใต้สายพานเดียว ทุกชิ้นตกถึงทั้งสองกรวย ตัวหนึ่งนับ อีกตัวรวมราคา แล้วพนักงานปลายทางประกอบ summary ไม่ใช่การแบ่งครึ่ง input และไม่ได้รับประกันว่าเร็วกว่า loop เสมอ แต่ช่วย reuse collectors มาตรฐานและรองรับ parallel semantics ตาม downstream

HOW: ตั้งชื่อผลลัพธ์แทน array ที่เดาความหมาย

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

public class Main {
    record Summary(long count, int total) {}

    static Summary summarize(List<Integer> values) {
        return values.stream().collect(Collectors.teeing(
            Collectors.counting(),
            Collectors.summingInt(Integer::intValue),
            Summary::new
        ));
    }

    public static void main(String[] args) {
        assert summarize(List.of(3, 5, 7)).equals(new Summary(3, 15));
        assert summarize(List.of()).equals(new Summary(0, 0));
    }
}

record ทำให้ลำดับ count กับ total อ่านได้ชัดกว่าคืน Object[] หรือ Map ที่ไม่มี type contract downstream collectors แต่ละตัวมี state ของตัวเอง ดังนั้น memory ไม่ได้เป็นศูนย์ เช่น pairing toList กับ groupingBy ยังเก็บข้อมูลมาก

เมื่อ loop ชัดกว่า

ใช้ teeing เมื่อมี reductions สองชุดที่เป็นอิสระและมี collector อยู่แล้ว หากเงื่อนไขของผลทั้งสองพันกันมาก loop เดียวพร้อม accumulator type เฉพาะอาจอ่านง่ายกว่าและลด overhead หากต้องการสามผล การซ้อน teeing ทำให้ type และ merger ลึก ควรสร้าง custom collector หรือ loop

parallel stream ไม่ได้กลายเป็นเร็วโดยอัตโนมัติ ต้องดู source spliterator, ค่า combine และขนาด input collector ที่ไม่ concurrent ยังทำงานด้วยการแยก accumulation แล้ว combine แต่ค่าใช้จ่ายอาจสูงกว่า sequential สำหรับข้อมูลเล็ก

ระวัง null ตาม downstream collector และ overflow ของ summingInt ถ้าผลรวมเกิน int ให้ใช้ summingLong หรือ reducing ที่เหมาะสม การรวม financial value ควรกำหนด type และ rounding policy ชัดเจน

Recap

  • teeing ส่ง element ทุกตัวเข้า collectors ทั้งสอง ไม่ได้แบ่ง input
  • merger ประกอบผลหลัง reductions เสร็จ
  • คืน named record เพื่อรักษาความหมายและ type safety
  • ใช้ loop/custom collector เมื่อผลมากหรือ logic พันกัน
  • วัด parallel stream และเลือก numeric type ตามขอบเขตข้อมูล

ลองแทนการ traverse list สองครั้งใน reporting path หนึ่งจุด แล้ว benchmark ด้วย input distribution จริงครับ