Java & Kotlin

Java 불변 객체(Immutable Object) 와 장단점

백엔드 유성 2024. 2. 4. 16:42

 

불변 객체(Immutable Object)는 한 번 생성되면 그 상태가 변하지 않는 객체를 의미합니다. 자바에서 가장 흔히 사용되는 불변 객체의 예로는 String 클래스가 있습니다. 이 글에서는 String 클래스를 통해 불변 객체의 개념을 살펴보고, 불변성의 장점과 단점에 대해 논의해 보겠습니다.

 

String 클래스: 불변 객체의 대표적인 예

 

String 클래스의 인스턴스는 불변 객체입니다. 즉, 한 번 생성된 String 객체의 내부 상태(저장된 문자열)는 변경할 수 없습니다. 예제를 통해 이를 확인해 보겠습니다.

String text = "abc";
text = text + "def";
System.out.println(text); // 출력 결과: "abcdef"

 

 

이 코드에서 text 변수는 처음에 "abc" 문자열을 참조합니다. 

"abc" 가 할당된 메모리 내부



그러나 text + "def" 연산이 실행되면 "abc" "def"가 결합된 새로운 String 객체 "abcdef"가 생성되고, text는 이 새 객체를 참조하게 됩니다.

메모리 내부

 

 

중요한 점은, 기존의 “abc” 문자열은 변경되지 않으며 새로운 문자열이 메모리에 생성된다는 것입니다.

 

 

이처럼 불변 객체는 한 번 생성된 후 내부 상태가 변하지 않으며, 변경할 수 없는 성질을 가지기 때문에 “불변 객체”라고 부릅니다.

불변 객체

 

불변성의 장점

1. Thread Safety (스레드 안전성)

  • 불변 객체는 한 번 생성된 이후 상태가 변경되지 않기 때문에, 여러 스레드가 동시에 접근해도 안전합니다. 이는 동기화(synchronization) 없이도 예측 가능하고 신뢰할 수 있는 동작을 보장합니다.

2. 재사용성

  • 불변 객체는 상태가 변하지 않으므로 여러 곳에서 동시에 재사용이 가능합니다. 이는 메모리 사용의 효율성을 높여주며, 특히 자주 사용되는 값들을 캐싱할 때 유리합니다.

3. 가독성 향상

  • 불변 객체는 상태가 변하지 않기 때문에 코드의 분석이 단순화됩니다. 이는 코드를 읽는 사람이 객체의 상태 변화를 추적할 필요가 없도록 만들어 가독성을 향상시킵니다. 불변성 덕분에 객체의 동작을 예측하기 쉬워져, 디버깅도 한결 간편해집니다.

4. 객체의 일관성 보장

  • 불변 객체는 생성된 이후 상태가 변하지 않으므로, 시스템 내에서 해당 객체를 사용하는 모든 부분에서 일관된 행동을 보장할 수 있습니다.

5. 보안성

  • 불변 객체는 외부에서 상태를 변경할 수 없기 때문에, 악의적인 외부 공격으로부터 객체의 내부 상태를 보호하는 데 유리합니다.

6. 부작용(Side Effect) 최소화

  • 불변 객체는 메서드 호출 시 부작용을 일으키지 않습니다. 즉, 동일한 메서드를 호출해도 항상 동일한 결과를 반환하며, 이는 함수형 프로그래밍과 잘 맞습니다. 함수형 프로그래밍에서는 부작용 없는 코드를 선호하기 때문에 불변 객체가 자주 사용됩니다.

 

불변성의 단점

1. 메모리 과도 사용

  • 불변 객체는 내부 상태가 변경될 때마다 새로운 객체를 생성합니다. 위의 예에서 문자열 "abc""def"를 결합할 때 새로운 객체가 생성되었고, 이로 인해 3개의 String 객체가 메모리에 존재하게 됩니다. 이는 불필요한 메모리 사용을 초래할 수 있습니다. 이러한 문제를 해결하기 위해 mutable 객체StringBuilderThread-safeStringBuffer를 사용할 수 있습니다.

2. 성능 저하

  • 상태 변경이 빈번한 경우, 불변 객체를 계속해서 생성하고 가비지 컬렉션(GC)이 이를 처리하게 되므로 성능 저하가 발생할 수 있습니다. 특히, 반복적인 연산에서 성능에 민감한 애플리케이션에서는 불변 객체 사용이 부담될 수 있습니다.

3. 구현 복잡성 증가

  • 복잡한 연산을 수행하면서 불변성을 유지하려면 객체 설계와 구현이 복잡해질 수 있습니다. 모든 필드를 불변으로 유지해야 하고, 내부 상태를 변경하는 메서드를 제공하지 않아야 하므로 설계 시 신경 써야 할 부분이 많아집니다.

 

불변 객체와 메모리 관리

GC mark & swep

 

불변 객체를 사용하면 메모리 관리에서 GC(Garbage Collection)가 중요한 역할을 합니다. 예를 들어, 위의 코드에서 text는 새로운 객체 "abcdef"를 참조하고 있으므로, 더 이상 참조되지 않는 "abc""abc" + "def"로 생성된 객체는 가비지 컬렉션의 대상이 됩니다. 즉, 불변 객체의 참조가 끊기면 Java의 GC가 이를 자동으로 수거해줍니다.


참조 변경과 객체의 불변성

불변 객체와 변수의 참조 변경을 혼동하지 않는 것이 중요합니다. 아래 예시에서 text는 불변 객체를 가리키지만, 그 자체는 가변 변수입니다.

반대로, 변수 자체를 불변으로 만들려면 text2 처럼 final 키워드를 사용할 수 있습니다.

String text = "abc"; // 가변 변수에 불변 객체를 할당
text = "def"; // 새로운 불변 객체를 가리키게 변경됨

final String text2 = "abc"; // 불변 변수에 불변 객체를 할당

 

불변 객체 생성

  1. 클래스를 final로 선언하여 상속을 허용하지 않도록 합니다. (상속을 통해 불변성을 깨트리는 것을 방지)
  2. 모든 필드를 final로 선언하여 필드 값이 변경되지 않도록 합니다.
  3. 필드 접근 제어자를 private으로 선언하고, 내부 상태를 변경하는 메서드를 제공하지 않습니다.

 

public final class ImmutableClass {
    private final String name;
    
    public ImmutableClass(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

위 코드에서 ImmutableClass는 불변 객체입니다. name 필드는 final로 선언되어 한 번 초기화되면 변경될 수 없고, getName() 메서드만을 제공하여 외부에서 상태를 조회할 수만 있습니다.

 

 

결론

불변 객체는 상태가 한 번 설정되면 변경되지 않기 때문에 안전하고, 신뢰성 있는 코드를 작성하는 데 매우 유용합니다. 특히, 스레드 안전성가독성 향상을 제공하며, 함수형 프로그래밍과의 호환성 덕분에 부작용 없는 코드를 작성할 수 있게 도와줍니다.

 

그러나 불변 객체를 과도하게 사용하면 성능 저하나 메모리 낭비가 발생할 수 있으므로, 변경이 빈번한 상황에서는 불변 객체 대신 가변 객체(StringBuilder, StringBuffer 등)를 사용하는 것이 좋습니다. 불변 객체와 가변 객체의 특성을 잘 이해하고, 적절한 상황에서 선택적으로 사용하는 것이 중요합니다.