นากกายสีมิ้นต์ถ่ายสำเนาแผนที่ลงกระดาษใหม่ ข้าง ๆ มีกรอบใสที่ยังเชื่อมกับต้นฉบับ

Java Map.copyOf คือ Snapshot แต่ unmodifiableMap ยังมองเห็นต้นฉบับเปลี่ยน

พี่โชว์Engineering

ทั้ง Map.copyOf(source) และ Collections.unmodifiableMap(source) ปฏิเสธ put ผ่านค่าที่คืนมา แต่รับประกันคนละเรื่องครับ ตัวแรกให้สำเนาที่แก้ไม่ได้ ส่วนตัวหลังเป็นหน้าต่างอ่านอย่างเดียวซึ่งยังมองเห็น source เปลี่ยนจาก alias อื่น

WHY: อ่านอย่างเดียวไม่เท่ากับค่าคงที่

ถ้าส่ง config ผ่าน unmodifiableMap แล้วเจ้าของ map เดิมแก้ค่าทีหลัง consumer จะเห็นการเปลี่ยนโดยไม่ได้เรียก setter ของตนเอง เหมือนติดกระจกหน้าตู้: คนหน้ากระจกหยิบของไม่ได้ แต่คนหลังตู้ยังสลับของได้ Map.copyOf ตัดการเชื่อมระดับโครงสร้าง ทำให้ snapshot เสถียรกว่า

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> source = new HashMap<>();
        source.put("timeout", 10);

        Map<String, Integer> view = Collections.unmodifiableMap(source);
        Map<String, Integer> snapshot = Map.copyOf(source);

        source.put("timeout", 20);
        assert view.get("timeout") == 20;
        assert snapshot.get("timeout") == 10;

        try {
            snapshot.put("retry", 3);
            throw new AssertionError("snapshot must reject mutation");
        } catch (UnsupportedOperationException expected) {
            // expected
        }
    }
}

Snapshot ยังไม่ deep copy

ถ้า value เป็น mutable list หรือ object ทั้งสอง map ยังอ้าง object เดิม การแก้ด้านในจึงมองเห็นได้ ต้อง copy value ตาม ownership contract เช่น List.copyOf หรือสร้าง value object ใหม่ และ Map.copyOf ปฏิเสธ key/value ที่เป็น null ซึ่งอาจทำให้ migration จาก HashMap ล้ม ต้อง validate ก่อน

อย่าพึ่ง iteration order ของ immutable map เพราะ contract ไม่รับประกันลำดับ หาก serialize config ต้องเลือก collection/การ sort ที่ระบุไว้ชัด และหาก source เป็น unmodifiable map ที่เหมาะสมอยู่แล้ว implementation อาจคืน object เดิมได้ จึงไม่ควรใช้ identity (==) เป็นหลักฐานว่าคัดลอกแล้ว

Recap

  • unmodifiableMap เป็น read-only view ที่ยังเห็น source เปลี่ยน
  • Map.copyOf ให้ snapshot โครงสร้างที่แก้ไม่ได้
  • ทั้งคู่ไม่ deep-copy value
  • ตรวจ null, ordering และ ownership ก่อนเปลี่ยน API