프로그래밍을 하다 보면 불변 객체(Immutable Object)라는 용어를 자주 접하게 된다. 특히 함수형 프로그래밍이나 멀티스레드 환경에서 자주 등장하는 개념이다. 이 문서에서는 불변 객체가 무엇인지, 왜 중요한지, 그리고 어떻게 만들 수 있는지를 알아보자.
1. 불변 객체란?
불변 객체(Immutable Object)는 한번 생성되면 그 상태를 변경할 수 없는 객체를 의미한다. 즉, 객체가 생성된 이후에는 그 내부 상태(필드 값 등)를 절대 바꿀 수 없다.
예를 들어, 자바의 String 클래스는 대표적인 불변 객체이다.
String a = "hello";
String b = a.toUpperCase(); // b는 "HELLO", a는 여전히 "hello"
위 코드에서 a.toUpperCase()를 호출했을 때, a의 값은 변하지 않고 새로운 문자열 객체가 생성되어 b에 저장된다. 이것이 불변 객체의 핵심이다.
2. 불변 객체가 필요한 이유
2.1 안정성 (Safety)
여러 스레드가 동시에 같은 객체를 참조해도, 그 상태가 바뀌지 않기 때문에 동기화 문제가 발생하지 않는다.
2.2 예측 가능성
객체가 변하지 않기 때문에 **사이드 이펙트(side effect)**를 방지할 수 있고 디버깅이 쉬워진다.
2.3 리팩토링과 유지보수 용이
객체의 상태가 불변이므로 코드 변경에 따른 버그 발생 가능성이 낮고, 유지보수가 용이하다.
3. 불변 객체 만들기
언어마다 불변 객체를 만드는 방식은 다르지만, 공통적으로 다음과 같은 원칙을 따른다.
- 모든 필드는 final 또는 const로 선언
- 외부에서 필드를 직접 변경할 수 없도록 private 접근제어자 사용
- 생성자를 통해서만 값을 설정
- 컬렉션은 불변 컬렉션으로 wrapping
- setter 메서드 사용 금지
- 상태 변경이 필요한 경우 새로운 객체 생성
4. 불변 객체 예제 코드 (Java)
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
5. 불변 객체의 장단점과 성능 이슈
장점
- 동시성에 강함: 불변 객체는 상태가 변하지 않기 때문에 동기화를 따로 고려하지 않아도 되어 멀티스레드 환경에서 유리하다.
- 버그 방지: 상태 변경이 불가능하므로 예기치 않은 변경으로 인한 버그를 방지할 수 있다.
- 테스트 용이성: 테스트 코드 작성 시 동일 입력에 대해 항상 같은 출력을 보장하므로 단위 테스트가 쉬워진다.
- 가독성과 유지보수성 향상: 코드의 흐름이 단순해지고 추적이 쉬워진다.
단점
- 메모리 사용 증가: 객체가 변경될 때마다 새로운 인스턴스를 생성해야 하므로 메모리 사용량이 증가할 수 있다.
- 성능 저하 가능성: 빈번한 객체 복사나 생성이 필요한 경우, 성능 저하를 유발할 수 있다.
- 복잡한 구조 처리 어려움: 깊은 중첩 구조를 가진 객체에서 변경이 필요할 경우, 전체 구조를 복사해야 하므로 코드가 복잡해질 수 있다.
성능 이슈 고려 사항
- 불변 객체는 변경이 자주 일어나는 데이터에는 적합하지 않다. 반면, 읽기 중심의 데이터나 공유 객체로 활용되는 경우에는 성능적으로 이점이 크다.
- JVM의 Garbage Collector 성능이나 Escape Analysis 최적화가 잘 작동하는 환경에서는 오히려 성능 저하 없이 불변 객체를 효율적으로 사용할 수 있다.
- 경우에 따라서는 불변과 가변 객체를 적절히 혼합하여 성능과 안정성을 균형 있게 설계하는 것이 바람직하다.
불변 객체는 단순히 값의 변경을 막는 기술이 아니다. 안정성, 예측 가능성, 유지보수성을 확보하기 위한 중요한 프로그래밍 기법이다. 최근의 대부분 언어에서는 불변 객체를 쉽게 만들 수 있는 문법적 지원을 제공하고 있으며, 멀티스레드 환경이나 함수형 프로그래밍에서는 불변성이 필수적이다.
'Programming > Java' 카테고리의 다른 글
JAVA synchronized 키워드와 ReentrantLock의 차이점 (멀티스레딩) (0) | 2025.04.15 |
---|---|
JAVA final, finally, finalize의 차이점 정리 (0) | 2025.04.15 |
JAVA equals()와 hashCode() 오버라이딩할 때 주의할 점 (1) | 2025.04.10 |
JAVA ArrayList vs LinkedList 차이점과 선택 기준 (0) | 2025.04.10 |
JAVA HashMap vs Hashtable vs ConcurrentHashMap 차이점 (0) | 2025.04.10 |