การสร้าง 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- groupingBy: การจัดหมวดหมู่องค์ประกอบ
- partitioningBy และ counting
- toMap, joining และ summarizing
- การสร้าง Collector แบบกำหนดเอง