불변 객체
생성 이후 상태(필드 값)가 절대 변하지 않는 객체입니다.
불변 클래스 만드는 방법
// 불변 클래스 5가지 조건
public final class Money { // 1. 클래스를 final로 — 상속 차단
private final long amount; // 2. 모든 필드를 final
private final String currency; // 3. 모든 필드를 private
public Money(long amount, String currency) { // 4. 생성자에서만 초기화
this.amount = amount;
this.currency = currency;
}
// 5. setter 없음, 변경 메서드는 새 객체 반환
public Money add(long value) {
return new Money(this.amount + value, this.currency);
}
public long getAmount() { return amount; }
public String getCurrency() { return currency; }
}
방어적 복사 — 가변 객체 포함 시
public final class Period {
private final Date start;
private final Date end;
// X 방어적 복사 없음 — 외부에서 Date를 변경하면 Period 내부 상태도 변함
public Period(Date start, Date end) {
this.start = start;
this.end = end;
}
// O 방어적 복사
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
}
public Date getStart() {
return new Date(start.getTime()); // getter에서도 방어적 복사
}
}
불변 객체의 장점
1. 스레드 안전 (Thread-Safe)
→ 상태가 변하지 않으므로 동기화 불필요
→ 여러 스레드가 동시에 읽어도 안전
2. 안전한 공유
→ 복사 없이 참조를 공유해도 되므로 메모리 효율적
3. 신뢰성
→ 메서드에 전달해도 값이 바뀔 걱정 없음
4. HashMap 키로 안전
→ 한번 넣으면 hashCode가 변하지 않음
5. 버그 발생 최소화
→ 상태 추적 불필요, 방어적 복사 감소
Java 내장 불변 클래스
String, Integer, Long, Double, Boolean — 래퍼 클래스 전부
BigInteger, BigDecimal
LocalDate, LocalTime, LocalDateTime — java.time 패키지
List.of(), Map.of(), Set.of() — 불변 컬렉션 (Java 9+)
불변 컬렉션 vs Collections.unmodifiableList
// Collections.unmodifiableList — 뷰(view): 원본 리스트가 변하면 같이 변함
List<String> original = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(original);
original.add("c");
System.out.println(view); // [a, b, c] — 원본 변경 반영됨
// List.of() — 진짜 불변: 어떤 방법으로도 변경 불가
List<String> immutable = List.of("a", "b");
immutable.add("c"); // X UnsupportedOperationException
핵심 정리
-
불변 클래스:
final class+ 모든 필드private final+ setter 없음 + 변경 시 새 객체 반환 -
가변 객체를 필드로 가지면 생성자·getter에서 방어적 복사 필수
-
상태가 없으니 스레드 안전 — 멀티스레드 환경에서 동기화 비용 없이 공유 가능