0Pricing
Java Academy · บทเรียน

การสร้าง Collector แบบกำหนดเอง

ใช้งานอินเทอร์เฟซ Collector เพื่อสร้างการรวมข้อมูลที่นำกลับมาใช้ซ้ำได้สำหรับกรณีใช้งานเฉพาะโดเมน

การสร้าง Collector แบบกำหนดเอง เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงสร้างตัวรวบรวมแบบกำหนดเอง

เมื่อไม่สามารถใช้ตัวรวบรวมในตัวกับโดเมนของคุณได้ ให้ใช้อินเทอร์เฟซ Collector<T, A, R> เพื่อสร้างตรรกะการรวมผลที่นำกลับมาใช้ซ้ำและประกอบร่วมกันได้ โดย T=ข้อมูลเข้า, A=ตัวสะสมที่เปลี่ยนแปลงได้ และ R=ผลลัพธ์

อินเทอร์เฟซตัวรวบรวม

ตัวรวบรวมมีองค์ประกอบ 4 ส่วน:

  • supplier() — สร้างตัวสะสมเริ่มต้น
  • accumulator() — รวมองค์ประกอบหนึ่งรายการเข้ากับตัวสะสม
  • combiner() — รวมตัวสะสมสองตัวเข้าด้วยกัน สำหรับสตรีมแบบขนาน
  • finisher() — แปลงตัวสะสมเป็นผลลัพธ์สุดท้าย
  • characteristics() — คำแนะนำเพื่อปรับปรุงประสิทธิภาพ

โรงงาน Collector.of()

วิธีที่ง่ายที่สุดในการสร้างตัวรวบรวมแบบกำหนดเองคือใช้ Collector.of() โดยไม่ต้องใช้อินเทอร์เฟซโดยตรง:

import java.util.stream.*;

// Collector that builds a comma-separated String
Collector<String, StringBuilder, String> csvCollector = Collector.of(
    StringBuilder::new,                     // supplier
    (sb, s) -> {
        if (sb.length() > 0) sb.append(',');
        sb.append(s);
    },                                       // accumulator
    (a, b) -> {
        if (a.length() > 0 && b.length() > 0) a.append(',');
        a.append(b); return a;
    },                                       // combiner
    StringBuilder::toString                  // finisher
);

String csv = Stream.of("Alice","Bob","Carol").collect(csvCollector);
System.out.println(csv); // Alice,Bob,Carol

ตัวรวบรวมแบบกำหนดเอง: TopN

สร้างตัวรวบรวมที่เก็บเฉพาะองค์ประกอบ N อันดับแรกตามตัวเปรียบเทียบ:

import java.util.*;
import java.util.stream.*;

static <T> Collector<T, ?, List<T>> topN(int n, Comparator<T> comp) {
    return Collector.of(
        () -> new ArrayList<T>(),
        (list, e) -> {
            list.add(e);
            list.sort(comp.reversed());
            if (list.size() > n) list.remove(list.size() - 1);
        },
        (a, b) -> {
            a.addAll(b);
            a.sort(comp.reversed());
            while (a.size() > n) a.remove(a.size() - 1);
            return a;
        },
        Collector.Characteristics.UNORDERED
    );
}

การใช้ตัวรวบรวม TopN

ใช้ตัวรวบรวมแบบกำหนดเอง TopN กับสตรีม:

List<Integer> top3 = Stream.of(5,2,9,1,7,3,8)
    .collect(topN(3, Comparator.naturalOrder()));
System.out.println(top3); // [9, 8, 7]

แฟล็กคุณลักษณะ

คุณลักษณะเสริมสามรายการช่วยปรับการเพิ่มประสิทธิภาพของสตรีม:

  • IDENTITY_FINISH — ฟังก์ชันขั้นสุดท้ายเป็นฟังก์ชันเอกลักษณ์ จึงข้ามได้
  • UNORDERED — ไม่จำเป็นต้องสนใจลำดับของผลลัพธ์
  • CONCURRENT — สามารถเรียกตัวสะสมพร้อมกันได้

การตั้งแฟล็กไม่ถูกต้อง เช่น ตั้ง CONCURRENT โดยไม่มีความปลอดภัยในการทำงานหลายเธรด จะทำให้เกิดข้อผิดพลาด

ตัวรวบรวมแบบกำหนดเอง: ค่าเฉลี่ยสะสม

รักษาค่าเฉลี่ยสะสมโดยไม่ต้องส่งผ่านข้อมูลเป็นครั้งที่สอง:

record RunningAvg(long count, double sum) {
    RunningAvg add(double v) { return new RunningAvg(count+1, sum+v); }
    RunningAvg merge(RunningAvg o) { return new RunningAvg(count+o.count, sum+o.sum); }
    double avg() { return count == 0 ? 0 : sum / count; }
}

Collector<Double, RunningAvg[], Double> avgCollector = Collector.of(
    () -> new RunningAvg[]{new RunningAvg(0, 0)},
    (a, v) -> a[0] = a[0].add(v),
    (a, b) -> { a[0] = a[0].merge(b[0]); return a; },
    a -> a[0].avg()
);

double avg = Stream.of(10.0, 20.0, 30.0).collect(avgCollector);
System.out.println(avg); // 20.0

การใช้อินเทอร์เฟซตัวรวบรวมโดยตรง

หากต้องการควบคุมมากขึ้น ให้ใช้อินเทอร์เฟซ Collector<T,A,R> เป็นคลาส:

import java.util.function.*;
import java.util.stream.*;

class StringJoiningCollector implements Collector<String, List<String>, String> {
    private final String delimiter;
    StringJoiningCollector(String delimiter) { this.delimiter = delimiter; }
    public Supplier<List<String>> supplier() { return ArrayList::new; }
    public BiConsumer<List<String>, String> accumulator() { return List::add; }
    public BinaryOperator<List<String>> combiner() {
        return (a,b) -> { a.addAll(b); return a; };
    }
    public Function<List<String>, String> finisher() {
        return list -> String.join(delimiter, list);
    }
    public Set<Characteristics> characteristics() { return Set.of(); }
}

เปรียบเทียบตัวรวบรวมแบบกำหนดเองกับตัวรวบรวมในตัว

ก่อนเขียนตัวรวบรวมแบบกำหนดเอง ให้ตรวจสอบว่าการประกอบตัวรวบรวมในตัวให้ผลลัพธ์เดียวกันได้หรือไม่ เพราะตัวรวบรวมเหล่านั้นผ่านการทดสอบและปรับประสิทธิภาพมาแล้ว:

// Instead of custom, compose:
Map<String, IntSummaryStatistics> stats = orders.stream().collect(
    Collectors.groupingBy(Order::status,
        Collectors.summarizingInt(Order::amount))
);
// This replaces many custom collector implementations

ความปลอดภัยของเธรดในตัวรวบรวมแบบกำหนดเอง

หากตั้งคุณลักษณะ CONCURRENT ตัวสะสมจะถูกเรียกจากหลายเธรดพร้อมกัน ดังนั้นตัวสะสมต้องปลอดภัยต่อการทำงานหลายเธรด เช่น ใช้ ConcurrentHashMap หรือ AtomicLong หากไม่ตั้ง CONCURRENT ตัวรวมจะจัดการการทำงานแบบขนานอย่างปลอดภัย

กรณีใช้งานจริง: ตัวรวบรวมฮิสโตแกรม

แบ่งค่าตัวเลขลงในช่วงของแมปฮิสโตแกรม:

Collector<Integer, Map<Integer, Long>, Map<Integer, Long>> histogram =
    Collector.of(
        LinkedHashMap::new,
        (map, val) -> map.merge(val / 10 * 10, 1L, Long::sum),
        (a, b) -> { b.forEach((k,v) -> a.merge(k,v,Long::sum)); return a; },
        Collector.Characteristics.IDENTITY_FINISH
    );

Map<Integer,Long> hist = Stream.of(5,15,25,12,8,22,18,3)
    .collect(histogram);
hist.forEach((bucket, cnt) -> System.out.println(bucket+"-"+(bucket+9)+": "+cnt));

ตรวจสอบความเข้าใจ

แฟล็กใดของคุณลักษณะตัวรวบรวมที่ระบุว่าฟังก์ชันขั้นสุดท้ายเป็นการแปลงแบบเอกลักษณ์และสามารถข้ามได้

สรุป: ตัวรวบรวมแบบกำหนดเอง

ประเด็นสำคัญ:

  • ตัวรวบรวม<T,A,R>: T=ข้อมูลเข้า, A=ตัวสะสม, R=ผลลัพธ์
  • ใช้ Collector.of() เพื่อสร้างตัวรวบรวมอย่างรวดเร็วโดยไม่ต้องสร้างคลาส
  • supplier/accumulator/combiner/finisher/characteristics คือองค์ประกอบ 5 ส่วน
  • การสิ้นสุดแบบเอกลักษณ์, UNORDERED และ CONCURRENT คือแฟล็กเพื่อเพิ่มประสิทธิภาพ
  • ควรประกอบตัวรวบรวมในตัวก่อน และสร้างแบบกำหนดเองเฉพาะเมื่อจำเป็น

คำถามที่พบบ่อย

บทเรียน “การสร้าง Collector แบบกำหนดเอง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การสร้าง Collector แบบกำหนดเอง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การสร้าง Collector แบบกำหนดเอง”

ใช้งานอินเทอร์เฟซ Collector เพื่อสร้างการรวมข้อมูลที่นำกลับมาใช้ซ้ำได้สำหรับกรณีใช้งานเฉพาะโดเมน คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การสร้าง Collector แบบกำหนดเอง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. groupingBy: การจัดหมวดหมู่องค์ประกอบ
  2. partitioningBy และ counting
  3. toMap, joining และ summarizing
  4. การสร้าง Collector แบบกำหนดเอง
← กลับไปที่ Java Academy