Java Collectors.toUnmodifiableMap: ปิดการแก้โครงสร้างหลัง stream
ผลจาก stream ที่เป็น configuration หรือ index มักไม่ควรถูกเพิ่มหรือลบหลังสร้างครับ Collectors.toUnmodifiableMap รวมข้อมูลเป็น map ที่ mutation ทางโครงสร้างโยน UnsupportedOperationException และปฏิเสธ null ช่วยให้ contract ปลายทางชัดขึ้น
WHY: ปิดฝากล่องหลังตรวจรายการ
เหมือนคัดของลงช่องแล้วปิดฝากล่องก่อนส่งต่อ คนรับมั่นใจว่าไม่มีใครแอบเพิ่มช่องระหว่างทาง แต่ถ้าของในช่องเป็นถุงที่เปิดได้ ของข้างในยังเปลี่ยนได้ ความ unmodifiable จึงครอบ map ไม่ได้ deep-freeze value
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
record Item(String key, int value) {}
public static void main(String[] args) {
Map<String, Integer> index = List.of(
new Item("mint", 1),
new Item("forest", 2)
).stream().collect(Collectors.toUnmodifiableMap(
Item::key,
Item::value
));
assert index.equals(Map.of("mint", 1, "forest", 2));
boolean immutable = false;
try {
index.put("amber", 3);
} catch (UnsupportedOperationException expected) {
immutable = true;
}
assert immutable;
boolean duplicateRejected = false;
try {
List.of(new Item("x", 1), new Item("x", 2))
.stream()
.collect(Collectors.toUnmodifiableMap(Item::key, Item::value));
} catch (IllegalStateException expected) {
duplicateRejected = true;
}
assert duplicateRejected;
}
}
Duplicate key ต้องเป็นการตัดสินใจ
Overload สอง argument โยน error เมื่อ key ซ้ำ ซึ่งดีเมื่อ duplicate คือข้อมูลเสีย หากธุรกิจยอมให้ซ้ำให้ใช้ overload ที่รับ merge function และตั้งชื่อ policy เช่น latest-wins หรือ max-value ชัดเจน อย่าใช้ฟังก์ชันเลือกค่าหลังสุดโดยไม่มีคำอธิบาย
Collector ไม่รับ null key หรือ null value และ map ไม่รับประกัน implementation หรือ iteration order หาก output ต้อง deterministic ให้ sort ก่อน serialize ส่วน value ที่ mutable ควร copy หรือ freeze ที่ boundary เอง
Recap
- ใช้
toUnmodifiableMapเมื่อโครงสร้างผลลัพธ์ควรปิดหลัง build - เลือก duplicate policy อย่างเปิดเผย
- ไม่ส่ง null เข้า collector
- ทำ defensive copy ของ mutable value หากต้องการ immutability จริง