“상태 변경 메서드가 없는 객체”라고 생각하면 가장 이해가 빠릅니다.
값이 바뀌어야 한다면 기존 객체를 수정하는 대신, 새 객체를 만들어 교체합니다.
이 특성 때문에 불변 객체는 동시성과 안정성 측면에서 강력합니다.
여러 스레드가 같은 객체를 동시에 봐도 “누가 값을 바꿀까?”를 걱정할 필요가 없습니다.
“불변 객체는 상태를 바꾸지 않음으로써
버그 가능성을 통째로 줄인다.”
👍 GOOD (불변 객체의 장점)
- 스레드 안전: 공유해도 안전(동기화 비용 감소)
- 예측 가능: 생성된 이후 값이 변하지 않으니 디버깅이 쉬움
- 참조 공유: 같은 값을 여러 곳에서 재사용 가능(캐싱/메모리 절약)
- 사이드 이펙트 감소: “어디서 바뀌었지?” 문제가 줄어듦
👎 BAD (가변 객체의 위험)
- 공유 객체가 여기저기서 수정되어 추적 불가
- 멀티스레드에서 경쟁 조건(race condition) 발생
- 동기화/락으로 해결하려다 성능 저하
- 테스트에서 상태가 남아 케이스 간 간섭
// 예시: Money를 불변 객체로 설계하기 // - 필드는 private final // - setter 없음 // - 값 변경은 새 객체 반환 final class Money { private final int amount; public Money(int amount) { if (amount < 0) throw new IllegalArgumentException("amount must be >= 0"); this.amount = amount; } public int amount() { return amount; } public Money plus(Money other) { return new Money(this.amount + other.amount); } public Money minus(Money other) { int next = this.amount - other.amount; if (next < 0) throw new IllegalStateException("insufficient"); return new Money(next); } } // 사용 예 // Money a = new Money(1000); // Money b = a.plus(new Money(500)); // a는 그대로, b는 새 객체
💡 TIP / 참고사항
불변 객체를 만들 때 가장 흔한 실수는 “필드는 final인데 내부 참조는 가변”인 경우입니다.
예를 들어 final List를 들고 있으면 리스트 자체는 바뀔 수 있습니다. 이런 경우에는 방어적 복사(defensive copy)나 불변 컬렉션으로 감싸야 진짜 불변이 됩니다.
✅ 핵심 요약
- ✔️ 불변 객체는 생성 후 내부 상태가 바뀌지 않는 객체이며, 변경은 새 객체 생성으로 처리한다.
- ✔️ 장점은 스레드 안전, 예측 가능성, 사이드 이펙트 감소다.
- ✔️ 구현 원칙은 private final, setter 금지, 새 객체 반환, 참조 타입 방어적 복사.