[Java 공식문서] 불변 객체

2025. 8. 15. 19:18·CS 및 기본 개념

불변 객체 개념과 필요성

불변 객체 기본 개념

불변 객체는 생성된 후 그 상태값이 변하지 않는 객체이다.

 

객체의 상태를 갱신하는 대신 내부 값을 변경할 수 없는 새로운 객체를 생성하는 방식이라고 보면 된다.

객체 생성의 오버헤드를 무시할 수는 없지만, 불변 객체와 관련된 효율성은 무시하기 어렵다.

 

스레드 간 동기화 문제 해결

기본적으로 멀티 스레드 환경에서, 어떤 상태에 대한 동기화 문제는 두 개 이상의 스레드에서 특정 상태를 경쟁적으로 변경하기 때문에 발생한다.

 

불변 객체는 객체 상태의 수정이 발생하지 않기 때문에 멀티 스레드 환경에서 동기화 문제가 발생하지 않는다. 

 

GC의 오버헤드 감소

기본적으로 JVM의 GC는 힙을 Generation(세대)으로 나눠서 관리한다

  • Young Generation : 새로 생성된 객체가 있는 공간. Minor GC에서 주로 관리함
  • Old Generation : 오래 살아남은 객체가 있는 공간. Full GC에서 주로 관리함

JVM의 Minor GC는 Young 객체뿐만 아니라 Old 객체가 참조 쓰기한 Young 객체도 추적해야 한다.

이를 위해서는 Old Generation을 탐색해야 하는데 그 크기가 매우 크기에 Old Generation을 여러개의 카드로 나눠서 관리한다.

Old 객체가 Young 객체를 참조 쓰기 했을 때 해당 Old 객체가 속한 카드만 Dirty로 변경하여 추적하는 구조이다.

 

그런데 불변 객체는 객체 생성 후 필드값이 변경되지 않기에 자연스럽게 Old 객체가 불변 Young 객체를 참조 쓰기 할 수 없다.

결과적으로 Dirty 상태의 Old Generation의 카드 수가 줄어들기에 탐색할 카드 수가 줄어드므로 GC의 오버헤드를 줄인다. 

 


불변 객체 생성 및 사용 예시

불변 객체 생성 규칙

불변 객체를 생성할 때 아래와 같은 규칙을 제시한다.

해당 규칙을 필수로 따라야하는 것은 아니지만, 규칙을 따르는 것이 안전하다.

1. 객체의 필드나 필드를 참조하는 객체를 직접 수정하는 메서드를 제공하지 않는다.

이는 setter를 포함한 메서드가 불변 객체 내부 상태를 수정하지 못하도록 하는 것을 의미한다

2. 모든 필드는 private과 final 키워드로 생성한다.

모든 필드를 private과 final로 생성하여 외부의 직접적 접근을 막고 변경되지 않도록 한다.

3. 클래스를 final 키워드로 생성하거나, 생성자를 private으로 생성한다.

아래와 같이 불변 객체는 final 키워드로 생성하는 것이 좋다.

더 정교하게 관리하려면, 생성자를 private으로 생성하고 팩토리 메서드로 객체 생성을 관리하는 것이 좋다.

public final class ImmutableClass{
    
    private ImmutableClass(){
    }

    public static ImmutableClass createNew(){
        return new ImmutableClass();
    }
}

4. 클래스 내 가변 객체의 참조를 외부와 공유하지 않는다.

모든 필드를 private과 final로 생성했더라도 해당 객체가 객체의 참조라면 값이 변경될 수 있다.

// 잘못된 불변 객체
public final class ImmutableClass{
    Date date;

    public ImmutableClass(Date date){
    	this.date = date;
    }

    public Date getDate(){
        return date;
    }
}

위 코드에서 ImmutableClass는 불변 객체로 보이지만 아래 상황에서 date 값이 변경될 수 있다.

public static void main(String[] args){
    ImmutableClass ic = new ImmutableClass(new Date());
    Date date = ic.getDate();
    date.setYear(1000);
}

이처럼 불변 객체 내부의 가변 객체의 참조를 외부와 공유하면 해당 값을 외부에서 수정할 수 있으므로 아래와 같이 해당 참조 객체의 복사본을 전달해야 한다.

// 잘못된 불변 객체
public final class ImmutableClass{
    Date date;

    public ImmutableClass(Date date){
    	this.date = date;
    }

    public Date getDate(){
        return new Date(date.getTime());
    }
}

 

불변 객체를 사용하지 않는 케이스

아래 경우는 불변 객체를 사용하지 않은 경우이다.

public class SynchronizedRGB{
    int rgb;
    String name;

    public SynchronizedRGB(int rgb, String name){
    	this.rgb = rgb;
        this.name = name;
    }
    
    public void set(int rgb, String name){
        this.name = name;
        this.rgb = rgb;
    }

    public synchronized int getRGB(){
        return rgb;
    }

    public synchronized String getName(){
        return name;
    }

    public SynchronizedRGB invert(){
        return new SynchronizedRGB(255-rgb, name);
    }
}

위 코드는 synchronized를 사용했지만 아래와 같은 상황에서 스레드 간 동기화 문제가 발생한다.

  1. 스레드 A가 set 호출
  2. 스레드 A가 getRGB 호출
  3. 스레드 B가 set 호출
  4. 스레드 A가 getName 호출

위 상황에서 A가 호출한 getName은 A가 set으로 설정한 값이 아닐 수 있다.

이는 set은 synchronized가 아니기 때문에 락을 사용하지 않고 내부 필드를 바꿀 수 있기 때문이다.

 

따라서 위 문제를 해결하려면 아래 세 가지 방식 중 하나를 사용하면 된다.

  • set을 synchronized method로 변경
  • getName과 getRGB를 synchronized 블록으로 묶어서 관리
  • 불변 객체 사용

 

불변 객체를 사용하는 케이스

아래는 불변 객체를 사용하여 기존 코드의 동기화 문제를 해결한 케이스이다.

public final class ImmutableRGB{
    int rgb;
    String name;

    public ImmutableRGB(int rgb, String name){
    	this.rgb = rgb;
        this.name = name;
    }
    
    public ImmutableRGB set(int rgb, String name){
        return new ImmutableRGB(rgb, name);
    }

    public int getRGB(){
        return rgb;
    }

    public String getName(){
        return name;
    }

    public ImmutableRGB invert(){
        return new ImmutableRGB(255-rgb, name);
    }
}

기존 코드와의 가장 큰 차이는 set이 현재 객체를 수정하는 것이 아닌 새로운 객체를 반환하는 것이다.

이를 통해서 동시성 프로그래밍에서 발생하는 동기화 문제를 보완할 수 있다.

 


마무리

이번에는 불변 객체에 대해서 알아보았다.

불변 객체는 GC 부담을 줄이고, 멀티 스레드 환경에서 동기화 문제를 효과적으로 해결할 수 있는 강력한 기술이다.

하지만 불변 객체를 생성하고 관리하는 것은 상당히 어렵고, 상태 변경이 잦은 경우 매번 새로운 불변 객체를 만들어야 하므로 메모리 측면에서 부담이 될 수 있다.

따라서 불변 객체는 가변 객체의 완벽한 대체가 될 수는 없다.

불변 객체를 제대로 이해하고, 적절한 상황에서 사용하는 것이 중요할 것이다.

'CS 및 기본 개념' 카테고리의 다른 글

[ cs ] 객체 지향 프로그래밍( OOP ) 4가지 특징  (1) 2025.09.04
[ Java 공식문서 ] Lock과 Executor  (1) 2025.08.20
[cs] Cache Control 헤더  (4) 2025.08.13
[ Java 공식문서 ] Liveness와 Guarded Block  (6) 2025.08.11
[cs] AWS lambda 기반의 Webhook  (1) 2025.08.07
'CS 및 기본 개념' 카테고리의 다른 글
  • [ cs ] 객체 지향 프로그래밍( OOP ) 4가지 특징
  • [ Java 공식문서 ] Lock과 Executor
  • [cs] Cache Control 헤더
  • [ Java 공식문서 ] Liveness와 Guarded Block
코드래곤
코드래곤
코드래곤 님의 블로그 입니다.
  • 코드래곤
    코드래곤 님의 블로그
    코드래곤
  • 전체
    오늘
    어제
    • 분류 전체보기 (61)
      • 알고리즘 (3)
        • 그리디 (1)
        • 그래프 (2)
      • 시스템 설계 (6)
      • CS 및 기본 개념 (17)
      • Docker (5)
      • Spring (23)
        • 백준 서비스 구현하기 (1)
        • 기초 개념 (14)
        • MSA (2)
        • JPA (1)
      • Dart (3)
      • Flutter (1)
      • Kubernetes (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
코드래곤
[Java 공식문서] 불변 객체
상단으로

티스토리툴바