Mutable Object = สถานะ (state) เปลี่ยนแปลงได้หลังสร้าง | Immutable Object = สถานะ **ไม่สามารถเปลี่ยนแปลงได้** หลังสร้าง
ตารางเปรียบเทียบ
| ลำดับ | หัวข้อ | Mutable Object | Immutable Object |
|---|---|---|---|
| 1 | พื้นฐาน | แก้ไข state ได้หลังสร้าง (setters, public fields) | ไม่สามารถแก้ไข state ได้หลังสร้าง |
| 2 | Thread Safety | ไม่ thread-safe (ต้อง synchronize เอง) | Thread-safe โดยธรรมชาติ (immutable = safe) |
| 3 | Final Class | ไม่จำเป็นต้อง final | ควรทำ class final (ป้องกัน subclass แก้ไข) |
| 4 | Fields | มี setter methods / non-final fields | fields ทั้งหมดเป็น private final |
| 5 | ตัวอย่าง | ArrayList, StringBuilder, Custom classes with setters | String, Integer, LocalDate, Custom immutable classes |
| 6 | Defensive Copy | ไม่จำเป็น (แต่ควรระวัง) | จำเป็นสำหรับ mutable fields (Date, Collections) |
| 7 | HashCode/Equals | อาจเปลี่ยนได้ (อันตรายใน HashMap/HashSet) | คงที่ตลอดชีวิต (ปลอดภัยเป็น key) |
ตัวอย่าง Immutable Class (Best Practice)
public final class ImmutableStudent {
private final String name;
private final int age;
private final List subjects; // mutable object ต้อง defensive copy
public ImmutableStudent(String name, int age, List subjects) {
this.name = name;
this.age = age;
// Defensive copy - ไม่ให้ภายนอกแก้ list ต้นฉบับได้
this.subjects = new ArrayList<>(subjects);
}
// Getters เท่านั้น (ไม่มี setters)
public String getName() { return name; }
public int getAge() { return age; }
// คืน defensive copy ป้องกันการแก้ไขจากภายนอก
public List getSubjects() {
return new ArrayList<>(subjects);
}
// with-er pattern สำหรับ "แก้ไข" (คืน object ใหม่)
public ImmutableStudent withAge(int newAge) {
return new ImmutableStudent(this.name, newAge, this.subjects);
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", subjects=" + subjects + "}";
}
}
ตัวอย่าง Mutable Class
public class MutableStudent {
private String name;
private int age;
private List subjects = new ArrayList<>();
public MutableStudent(String name, int age) {
this.name = name;
this.age = age;
}
// มี setters - สามารถเปลี่ยน state ได้
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public void addSubject(String subject) { subjects.add(subject); }
public List getSubjects() { return subjects; } // อันตราย! คืน reference ตรงๆ
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", subjects=" + subjects + "}";
}
}
การทดสอบ
public class Test {
public static void main(String[] args) {
// Immutable
ImmutableStudent im = new ImmutableStudent("John", 20, List.of("Math", "Physics"));
System.out.println("Original: " + im);
ImmutableStudent im2 = im.withAge(21);
System.out.println("After withAge: " + im2);
System.out.println("Original unchanged: " + im);
// พยายามแก้ไข (compile error ถ้า uncomment)
// im.setAge(25); // Error: no setter
// im.getSubjects().add("Chemistry"); // ไม่ส่งผลต่อ object เดิม (defensive copy)
// Mutable
MutableStudent mut = new MutableStudent("Jane", 22);
mut.addSubject("Biology");
System.out.println("\nMutable: " + mut);
// อันตราย: external modification
List ref = mut.getSubjects();
ref.add("Hacked!"); // แก้ไข internal state ได้!
System.out.println("After external hack: " + mut);
}
}
Output
Original: Student{name='John', age=20, subjects=[Math, Physics]}
After withAge: Student{name='John', age=21, subjects=[Math, Physics]}
Original unchanged: Student{name='John', age=20, subjects=[Math, Physics]}
Mutable: Student{name='Jane', age=22, subjects=[Biology]}
After external hack: Student{name='Jane', age=22, subjects=[Biology, Hacked!]}
ทำไม Immutable ดีกว่า?
- ✅ **Thread-safe** ใช้ร่วมกันได้โดยไม่ต้อง lock
- ✅ **Predictable** state ไม่เปลี่ยน → debug ง่าย
- ✅ **Safe as Map/Set key** hashCode ไม่เปลี่ยน
- ✅ **Failure atomicity** ถ้า exception ขึ้น object ไม่ corrupt
- ✅ **Cache-friendly** ใช้ซ้ำได้ (String pool, Integer cache)
เมื่อไหร่ควรใช้ Mutable
- Performance critical: หลีกเลี่ยง object allocation เยอะ (game loop, high-frequency trading)
- Large data structures: สร้างใหม่ทุกครั้งแพงเกิน (ใช้ persistent data structures แทน)
- JavaBeans / Framework requirement: บาง framework ต้องการ setters (Hibernate, Jackson)
- Builder pattern: mutable builder → immutable result
Immutable Classes ใน JDK
- String, جميع Wrapper classes (Integer, Long, Double, ...)
- java.time.* (LocalDate, LocalDateTime, Instant, ...)
- BigInteger, BigDecimal
- UUID, Class, StackTraceElement