본문 바로가기
Java/객체 지향

값 객체(Value Object)와 불변성

by 정구정구 2026. 6. 12.

값 객체(Value Object)란?

 1000원짜리 지폐 A와 1000원짜리 지폐 B가 있다.

  • A와 B는 서로 다른 객체
  • 하지만 둘 다 1000원이라는 가치를 가짐

A와 B가 다른 객체라는게 아니라 가지고 있는 값(value)가 중요!! 

public final class Money {
    private final long amount; // 불변성

    public Money(long amount) {
        if (amount < 0) throw new IllegalArgumentException("금액은 0 이상");
        this.amount = amount;
    }

    public long amount() { return amount; }

    @Override
    public boolean equals(Object o) { // amount가 같으면 같다고 정의해주기
        if (this == o) return true; // 자기 자신 통과
        if (!(o instanceof Money other)) return false; // null 타입 거부 + 캐스팅
        return amount == other.amount; // 의미상 동등성
    }

    @Override
    public int hashCode() { // equals를 고치면 꼭 고쳐야 함!! 
        return Long.hashCode(amount);
    }
}

 

* hashCode : HashMap의 키나 HashSet의 원소 쓰일 때, 기준이 되는 값. 

 

값 객체(Value Object)의 장점

1. 불변성을 보장한다. (뒤에서 자세히 설명)

2. 가독성을 올려주고 실수를 방지한다.

public class Customer {
    private final String name;
    private Money money;             // int → Money

    public void pay(Money amount) {  // int → Money
        // … 
    }
}

 

코드를 위와 같이 수정하면, 코드만 봐도 변수가 돈을 의미한다는 것을 파악할 수 있고, 다른 int 값과 섞어 쓰는 실수를 막아준다.

 

불변성

값 객체는 변화하지 않는다는 성질을 가져야 한다. 그렇기 때문에 값 객체를 불변 객체로 만들어야 한다. 따라서 setter가 존재해서는 안되며, 가지고 있는 상태는 외부에 공개해서는 안된다.

 

불변 객체 3대 규칙

  • 모든 필드 final + 생성자에서만 초기화
  • 수정이 필요하면 새 객체 반환
  • final class : 상속으로 우회하는 것을 막음
public final class Money {
    private final long amount;

    public Money(long amount) {
        if (amount < 0) throw new IllegalArgumentException("금액은 0 이상");
        this.amount = amount;
    }

    public long amount() { return amount; }

    public Money add(Money other) {
        return new Money(this.amount + other.amount);
    }

    public Money minus(Money other) {
        if (this.amount < other.amount) {
            throw new IllegalStateException("잔액 부족");
        }
        return new Money(this.amount - other.amount);
    }

    public boolean isLessThan(Money other) {
        return this.amount < other.amount;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money other)) return false;
        return amount == other.amount;
    }

    @Override
    public int hashCode() {
        return Long.hashCode(amount);
    }
}

* 불변을 적용한 Money 값 객체

 

값 객체를 불변으로 만들어야 하는 이유 

  • 별칭(alias) 버그 차단 : 같은 Money를 여러 곳에서 참조했을 때, 한 쪽이 바뀌면 바른 쪽이 조용히 따라 바뀌는 사고 차단
  • equals / hashCode 무결성 : HashMap 키로 넣은 뒤 값이 바뀌면 원래 칸을 못 찾는 실패 방지(해시가 고정이므로)

 

불변의 장점

  • 멀티 스레드 안전 (동기화 고민 없음)
  • HashMap 키로 안전