왜 불변 객체를 써야 할까?

2025. 5. 3. 09:36·언어/Java

왜 불변 객체가 중요한가? 

개발을 하다 보면 객체의 상태를 바꾸는 일이 매우 흔하다.
그러나 이 상태 변경이 언제, 어디서, 왜 발생했는지를 추적하다 보면 복잡해지고,
대부분의 버그는 “예상하지 못한 상태 변화”에서 비롯된다.

 

이 때문에 많은 개발자들이 다음과 같이 말한다.

“가능한 한 객체는 불변(immutable)하게 만들어라.”

 

그렇다면 불변 객체란 무엇이며,
왜 그렇게들 불변을 강조하는가?

✅ 불변 객체란?

한 번 생성되면 내부 상태가 절대로 바뀌지 않는 객체를 의미한다. 

String name = "gugbab2";
String upper = name.toUpperCase(); // name은 그대로, upper는 "경호"의 대문자

→ String은 대표적인 불변 객체이다.
toUpperCase()를 호출해도 원본은 변경되지 않는다.

🔍 불변 객체를 사용해야 하는 이유

🔒 스레드 안전(Thread-Safe)하다

불변 객체는 상태가 변하지 않기 때문에, 

멀티 스레드 환경에서도 락(lock) 을 걸 필요 없이 안전하게 공유할 수 있다. 

LocalDate date = LocalDate.now(); // LocalDate도 불변 객체이다

서버에서 여러 요청이 동시에 이 객체를 참조하더라도 문제가 발생하지 않는다.

🔍 상태 추적이 용이하다

불변 객체는 값이 절대로 바뀌지 않기 때문에,
"어디서 값이 바뀌었는가?"를 고민할 필요가 없다.

Money price = new Money(1000);
Money discounted = price.applyDiscount(10); // price는 1000, discounted는 900

→ applyDiscount()는 원본을 변경하지 않고, 새로운 인스턴스를 반환한다.
→ 디버깅과 유지보수가 쉬워진다.

🚫 부작용(side effect)이 없다

불변 객체는 내부 상태를 변경하지 않기 때문에,
함수를 순수 함수(pure function)처럼 만들 수 있다.

순수 함수란,
동일한 입력값에 대해 항상 같은 결과를 반환하고, 외부 상태에 영향을 주지 않는 함수이다.

이러한 함수는 예측 가능하며, 테스트하기 쉽다.

🧩 컬렉션에서 안전하게 사용할 수 있다

자바의 HashMap, HashSet과 같은 컬렉션은 내부적으로
hashCode()와 equals()를 이용하여 객체를 관리한다.

그러나 객체를 key로 넣은 후 내부 값을 바꾸게 되면
해시값이 바뀌고, 조회나 삭제가 불가능해지는 문제가 발생한다.

User u = new User("gugbab2");
Set<User> set = new HashSet<>();
set.add(u);

u.setName("변경됨"); // 해시값 변경

set.contains(u); // false
set.remove(u);   // false

→ 불변 객체라면 이러한 문제는 발생하지 않는다.

♻️ 캐싱, 공유, 재사용에 유리하다

불변 객체는 내부 상태가 바뀌지 않기 때문에,
객체 풀, 캐시 등의 전략을 통해 메모리 효율성과 성능을 높일 수 있다.

Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // true

→ 같은 값을 재사용하기 때문에 불필요한 객체 생성을 줄일 수 있다.

✅ 언제 불변 객체를 사용해야 하는가?

다음과 같은 경우에는 불변 객체를 사용하는 것이 강력하게 권장된다.

📦 DTO, 응답 객체를 설계할 때

 

  • 클라이언트에 반환되는 응답 값은 변경되면 안 되는 값이다.
  • 데이터를 수신한 이후에는 읽기만 가능해야 신뢰할 수 있는 응답이 된다.
record UserResponse(String name, int age) {} // Java 16+ Record는 불변

 

🧩 해시 기반 컬렉션에서 key나 요소로 사용할 때

  • HashMap, HashSet 등은 객체의 hashCode()와 equals() 값을 기반으로 내부 버킷을 구성한다.
  • 객체를 key 또는 요소로 넣은 후 내부 상태가 변경되면,
    → 해시값이 달라져서 조회, 삭제, 수정이 모두 실패할 수 있다.
  • 특히 equals()와 hashCode()를 오버라이딩한 객체는 내부 필드가 바뀌면
    동등성 판단 기준 자체가 바뀌므로 매우 위험하다.→ 따라서 컬렉션에 넣을 객체는 동등성 기준이 되는 필드가 변하지 않아야 하며,
    이를 위해 불변 객체로 설계하는 것이 가장 안전하다.
User u = new User("경호");
Set<User> set = new HashSet<>();
set.add(u);

u.setName("다른 이름"); // ❌ hashCode 변경

set.contains(u); // false
set.remove(u);   // false

🧱 도메인 모델의 Value Object

  • DDD(Value Object)는 의미 있는 값을 표현하는 객체이며, 식별자(ID)가 없고,
    오직 내부 값 자체로 동등성을 판단한다.
  • 상태가 변하게 되면 VO 간의 비교 자체가 불가능해지거나 의미가 퇴색된다.

→ 그래서 VO는 반드시 불변으로 만들어야 설계 의미를 온전히 지킬 수 있다.

public class Money {
    private final int amount;
    public Money add(Money other) { return new Money(this.amount + other.amount); }
}

🧪 테스트에서의 예측 가능성이 중요할 때

 

  • 테스트는 입력에 대해 결과가 항상 같아야 신뢰할 수 있다.
  • 불변 객체는 상태가 변하지 않기 때문에 테스트 케이스가 안정적이고,
    외부 조건이나 실행 순서에 의존하지 않는 테스트 구성이 가능하다.

✨ 마무리하며

불변 객체는 처음에는 다소 불편하게 느껴질 수 있다.
그러나 프로젝트가 커지고 복잡해질수록,
유지보수성과 안정성 면에서 압도적인 장점을 제공한다.

“이 객체의 상태가 바뀌면 안 되는가?”
“이 값을 누가 언제 바꿀 가능성이 있는가?”

이런 질문이 들 때, 불변 객체를 사용하는 것을 적극 고려해야 한다.

 

'언어 > Java' 카테고리의 다른 글

왜 List<String>은 List<Object>가 될 수 없을까? (제네릭과 와일드카드가 공존하는 이유)  (0) 2025.05.03
happens-before란 무엇인가?  (0) 2025.05.02
ABA 문제란? - CAS 에서 터질 수 있는 진짜 함정  (0) 2025.05.02
Executor 프레임워크2 - Executor 프레임워크가 등장한 이유  (0) 2025.04.30
Executor 프레임워크1 - 스레드를 직접 사용할 때 문제점  (0) 2025.04.30
'언어/Java' 카테고리의 다른 글
  • 왜 List<String>은 List<Object>가 될 수 없을까? (제네릭과 와일드카드가 공존하는 이유)
  • happens-before란 무엇인가?
  • ABA 문제란? - CAS 에서 터질 수 있는 진짜 함정
  • Executor 프레임워크2 - Executor 프레임워크가 등장한 이유
gugbab2
gugbab2
국밥과 커피를 사랑하는 개발자 gugbab2 입니다.
  • gugbab2
    개발하는 프로 국밥러
    gugbab2
  • 전체
    오늘
    어제
    • 분류 전체보기 (48)
      • 프로젝트 (5)
      • 생각정리 (0)
      • Backend (0)
        • 소소한 백엔드 개발 이야기 (0)
        • Spring (0)
        • JPA (0)
      • 언어 (12)
        • Java (12)
      • CS (17)
        • 네트워크 (17)
      • 아키텍처 (14)
        • OOP (14)
        • TDD (0)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    책
    공부하자
    설계
    happends-before
    언어
    오브젝트
    비동기
    동시성
    개발
    스위치
    비전공
    하드웨어
    개발블로그
    토큰
    방화벽
    LACP
    타입
    개발자
    제네릭
    MC-LAG
    github
    객체
    의존성
    자바
    프로젝트
    네트워크
    새해
    Executor
    리뷰
    객체지향
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
gugbab2
왜 불변 객체를 써야 할까?
상단으로

티스토리툴바