Java에서의 불변 객체(Immutable Object)

Sony·2025년 1월 30일
0

☕️ JAVA

목록 보기
2/4
post-thumbnail

Java에서는 불변 객체(Immutable Object)를 활용하면 객체의 상태를 안전하게 유지할 수 있다.
특히, 멀티스레드 환경에서 동기화 문제를 피하고, 안정적인 코드 작성을 가능하게 한다.
이번 포스팅에서는 불변 객체가 무엇인지, 어떻게 만드는지, 그리고 불변 객체를 사용해야 하는 이유를 알아보자.


1. 불변 객체란?

불변 객체(Immutable Object)란 한 번 생성되면 상태를 변경할 수 없는 객체 를 의미한다.
즉, 객체 내부의 필드 값을 변경할 수 없으며, 생성 시 설정된 값이 변하지 않는다.

대표적인 불변 객체 예시

  • String
  • Wrapper Class (Integer, Double, Boolean 등)
  • java.time 패키지의 날짜 클래스 (LocalDate, LocalTime, LocalDateTime 등)

예제: String은 불변 객체이다

public class ImmutableStringExample {
    public static void main(String[] args) {
        String str = "Hello";
        str = str.concat(" World"); // 새로운 객체가 생성됨
        System.out.println(str); // "Hello World"
    }
}

위 코드에서 "Hello" 문자열은 변경되지 않고, concat 메서드를 호출하면 새로운 문자열 객체 가 생성된다. 즉, 기존 str 객체의 값이 바뀌는 것이 아니라, 새로운 참조값이 str에 저장되는 것이다.


2. 불변 객체를 만드는 방법

Java에서 불변 객체를 만들려면 다음 규칙을 따라야 한다.

  1. 필드는 private final로 선언한다.
  2. Setter 메서드를 제공하지 않는다.
  3. 생성자를 통해서만 값을 설정한다.
  4. 객체의 내부 참조 타입 필드도 변경되지 않도록 방어적 복사를 수행한다.

불변 객체 예제

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
  • nameage 필드는 private final로 선언되어 한 번 설정되면 변경할 수 없다.
  • 생성자를 통해 필드 값을 설정하고, 이후에는 Setter 메서드를 제공하지 않는다.
  • 객체의 상태를 변경할 방법이 없으므로 완전한 불변 객체가 된다.

3. 불변 객체와 가변 객체의 차이

불변 객체와 가변 객체의 차이를 비교해 보자.

가변 객체 (Mutable Object)

class MutablePerson {
    private String name;

    public MutablePerson(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name; // 상태 변경 가능
    }

    public String getName() {
        return name;
    }
}

MutablePerson 클래스는 setName() 메서드를 통해 객체의 상태를 변경할 수 있다.

불변 객체 (Immutable Object)

public final class ImmutablePerson {
    private final String name;

    public ImmutablePerson(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

ImmutablePerson 클래스는 한 번 설정된 name 값을 변경할 방법이 없기 때문에, 객체의 상태가 유지된다.


4. 내부 참조 타입 필드의 불변성 유지 (방어적 복사)

만약 클래스 내부에 참조형 데이터(예: List, Date 등) 가 있다면, 단순히 final을 사용해도 불변성을 보장할 수 없다.

잘못된 예제

import java.util.ArrayList;
import java.util.List;

public final class WrongImmutableClass {
    private final List<String> items; // final이지만 불변 객체가 아님

    public WrongImmutableClass(List<String> items) {
        this.items = items; // 외부 리스트가 변경되면 내부 상태도 변경됨
    }

    public List<String> getItems() {
        return items; // 외부에서 변경 가능
    }
}

위 코드에서 getItems()를 호출하면 원본 리스트의 참조값이 그대로 반환되므로, 외부에서 리스트를 변경할 수 있다.

방어적 복사(Defensive Copy) 적용

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class ImmutableClass {
    private final List<String> items;

    public ImmutableClass(List<String> items) {
        this.items = new ArrayList<>(items); // 새로운 리스트로 복사
    }

    public List<String> getItems() {
        return Collections.unmodifiableList(items); // 변경 불가능한 리스트 반환
    }
}
  • 생성자에서 리스트를 새로운 객체로 복사 하여 원본 리스트의 변경이 영향을 주지 않도록 한다.
  • getItems()에서 Collections.unmodifiableList()를 사용하여 읽기 전용 리스트를 반환 한다.

이제 ImmutableClass는 완전히 불변성을 유지할 수 있다.


5. 불변 객체의 장점

1) 멀티스레드 환경에서 안전(Thread-Safe)

불변 객체는 상태가 변경되지 않기 때문에 동기화(Synchronization) 없이도 안전하게 사용할 수 있다.
이 때문에 String, Integer, LocalDate 같은 클래스들은 멀티스레드 환경에서도 문제없이 사용된다.

2) 불변 객체는 신뢰성이 높다

객체의 상태가 변하지 않으므로, 사이드 이펙트(Side Effect) 없이 안전한 코드 작성을 할 수 있다.
즉, 한 번 생성된 객체는 안심하고 사용할 수 있다.

3) 해시 기반 컬렉션에서 안정적

불변 객체는 equals()hashCode() 값이 변하지 않으므로, HashMap이나 HashSet의 키(Key)로 사용하기 적합하다.


6. 결론

Java에서 불변 객체(Immutable Object)를 사용하면 객체의 상태를 안전하게 유지할 수 있으며, 멀티스레드 환경에서도 동기화 없이 안전하게 활용할 수 있다. 또한 불변 객체를 만들 때 방어적 복사(Defensive Copy)를 활용하면 내부 참조 데이터까지 안전하게 보호할 수 있다.

따라서 불변 객체를 잘 활용하면 코드의 안정성이 높아지고, 유지보수성이 개선된다.

profile
Bamboo Tree 🎋 : 대나무처럼 성장하고 싶은 개발자, Sony입니다.

0개의 댓글

관련 채용 정보