불변 객체는 어떻게 만들고 장점은 무엇일까?
소프트웨어 개발, 특히 멀티스레드 환경에서는 데이터 일관성과 안전성이 매우 중요합니다.
이때 한 번 생성된 후 내부 상태가 변하지 않는 불변 객체(Immutable Object)가 큰 역할을 합니다.
이 글에서는 불변 객체가 무엇인지, 어떻게 만드는지, 그리고 그 장단점과 면접에서 나올 수 있는 질문들에 대해 살펴보겠습니다.
불변 객체란 무엇인가?
불변 객체는 생성 이후 내부 상태나 데이터가 절대 변경되지 않는 객체입니다.
한 번 값이 설정되고 객체의 상태를 바꾸려면 반드시 새로운 객체를 생성해야 합니다.
대표적인 예로, Java의 String, Integer, BigDecimal 등이 있습니다.
불변 객체를 사용하는 이유는 주로 멀티스레드 환경에서 동기화 문제를 피하고, 코드의 예측 가능성을 높이며, 의도치 않은 부작용(side effect)을 방지하기 위함입니다.
데이터가 한 번 결정되면 변하지 않으므로, 여러 스레드가 동시에 읽더라도 안전하게 사용할 수 있습니다.
불변 객체 만드는 방법
불변 객체를 올바르게 만들기 위해서는 몇 가지 원칙을 철저히 지켜야 합니다.
일반적으로 다음과 같은 규칙을 따릅니다.
- 클래스 자체를 final로 선언
상속을 통해 내부 상태가 변경되는 것을 방지합니다. - 필드를 private과 final로 선언
외부에서 필드에 직접 접근하거나 수정하지 못하도록 하며, 객체 생성 시 한 번만 값이 할당되도록 합니다. - 생성자를 통해 모든 필드를 초기화
객체가 생성될 때 모든 필드의 값이 정해지며, 이후에는 변경할 수 없습니다. - Setter 메서드를 제공하지 않는다
객체의 상태를 변경할 수 있는 메서드를 아예 제공하지 않아야 합니다. - 가변 객체를 포함할 경우 방어적 복사(Defensive Copy)를 수행
만약 객체 내부에 가변 객체(예를 들어, Date나 List)가 있다면, 생성자나 getter 메서드에서 복사본을 만들어 반환하여 외부에서 내부 데이터를 변경하지 못하도록 해야 합니다.
예를 들어, Java에서 간단한 불변 객체를 만드는 코드는 다음과 같습니다.
public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// 가변 객체인 List에 대해 방어적 복사 수행
this.hobbies = new ArrayList<>(hobbies);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
// 내부 상태 보호를 위해 복사본 반환
return new ArrayList<>(hobbies);
}
// 상태를 변경할 필요가 있을 때는 새로운 객체를 생성하여 반환
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge, this.hobbies);
}
}
위 예제처럼, 불변 객체는 한 번 생성된 후 내부 데이터를 변경할 수 없으므로, 멀티스레드 환경에서도 안전하게 사용할 수 있습니다.
불변 객체의 장점과 단점
불변 객체를 사용하면 여러 가지 이점을 얻을 수 있지만, 동시에 몇 가지 단점도 존재합니다.
1) 장점
- 스레드 안전성
불변 객체는 상태가 변하지 않기 때문에, 여러 스레드가 동시에 접근하더라도 동기화 문제 없이 사용할 수 있습니다.
별도의 락(lock)이나 동기화 코드 없이도 안전한 데이터 공유가 가능합니다. - 부작용 최소화
객체의 상태가 변경되지 않으므로, 한 객체를 여러 곳에서 참조해도 값이 일관되게 유지됩니다.
이는 코드의 예측 가능성과 디버깅 용이성을 높입니다. - 해시값 안정성
불변 객체의 경우 생성 시 해시값이 결정되며, 이후 변경되지 않기 때문에 HashMap, HashSet 등 해시 기반 컬렉션에 안전하게 사용할 수 있습니다. - 캐싱 및 재사용
한 번 생성된 불변 객체는 동일한 값으로 여러 곳에서 재사용할 수 있어, 불필요한 객체 생성을 줄이고 성능을 최적화할 수 있습니다.
2) 단점
- 객체 생성 비용 증가
불변 객체는 값을 변경할 때마다 새로운 객체를 생성해야 합니다.
데이터 변경이 빈번한 경우, 그로 인한 메모리 사용량 증가와 객체 생성 비용이 성능에 부정적인 영향을 미칠 수 있습니다. - 대규모 데이터 처리의 비효율성
크거나 복잡한 데이터 구조를 불변 객체로 만들 경우, 일부 값만 변경되어도 전체 객체를 새로 생성해야 하므로 비효율적일 수 있습니다. - 가변 객체와의 혼용 문제
불변 객체 내부에 가변 객체가 포함된 경우, 방어적 복사를 제대로 수행하지 않으면 내부 상태가 외부에서 변경될 위험이 있습니다.
이러한 단점을 극복하기 위해 상황에 따라 불변 객체와 가변 객체를 적절히 조합하여 사용하는 전략이 필요합니다.
예를 들어, 값이 자주 변경되는 중간 과정에서는 가변 객체를 활용하고, 최종 결과를 불변 객체로 만들어 반환하는 방식으로 설계할 수 있습니다.
면접 대비: 예상 꼬리 질문과 답변
면접에서는 불변 객체에 관한 면접에서는 기본 개념, 생성 방법, 장점과 단점, 그리고 실제 구현 시 고려해야 할 사항들을 물을 수 있습니다.
Q1. 불변 객체를 만들기 위해 어떤 원칙을 따라야 하며, 그 이유는 무엇인가요?
"불변 객체를 만들기 위해서는 클래스와 필드를 final로 선언하고, 모든 필드를 생성자에서 초기화하며 setter 메서드를 제공하지 않아야 합니다.
또한, 만약 가변 객체를 포함한다면 방어적 복사를 수행하여 외부에서 객체의 내부 상태를 변경하지 못하도록 해야 합니다.
이러한 원칙을 따르면, 객체 생성 후에는 상태가 변경되지 않기 때문에 멀티스레드 환경에서도 안전하며, 데이터 일관성과 예측 가능성이 보장됩니다."
Q2. 불변 객체를 사용하면 어떤 장점이 있고, 단점은 무엇인가요?
"불변 객체의 주요 장점은 스레드 안전성, 부작용 최소화, 해시값 안정성, 그리고 캐싱의 용이성입니다.
객체가 생성된 후 상태가 변하지 않으므로, 여러 스레드가 동시에 접근해도 문제가 발생하지 않고, 함수 호출 시 예상치 못한 부작용을 방지할 수 있습니다.
그러나 단점으로는, 값이 변경될 때마다 새로운 객체를 생성해야 하므로 객체 생성 비용이 증가하고, 자주 변경되는 데이터에는 비효율적일 수 있다는 점이 있습니다."
Q3. Java에서 불변 객체를 구현할 때, 가변 객체를 포함한다면 어떻게 처리해야 하나요?
"불변 객체 내부에 가변 객체가 포함되어야 하는 경우, 생성자와 getter에서 반드시 방어적 복사를 수행해야 합니다.
예를 들어, 리스트나 날짜 객체와 같이 외부에서 수정 가능한 객체를 필드로 사용할 때는 생성자에서 원본 데이터를 복사해서 저장하고, getter에서는 복사본을 반환하여 외부에서 내부 상태를 변경할 수 없도록 설계해야 합니다."
Q4. 불변 객체와 가변 객체를 어떻게 적절히 조합해서 사용할 수 있을까요?
"불변 객체는 한 번 설정된 상태를 변경하지 않아 안정성을 보장하지만, 값이 자주 변경되는 경우 매번 새로운 객체를 생성하는 비용이 발생할 수 있습니다.
이러한 경우, 내부 로직에서는 가변 객체를 사용하여 효율적으로 변경 작업을 수행한 후, 최종 결과를 불변 객체로 변환해 반환하는 방식을 사용할 수 있습니다.
예를 들어, 문자열 조작이 많은 경우 StringBuilder를 사용해 임시 작업을 수행하고, 최종 결과를 String으로 만들어 불변성을 보장하는 방법이 있습니다."
정리
불변 객체는 코드의 안정성과 예측 가능성을 높이고, 멀티스레드 환경에서 동기화 문제를 최소화하는 중요한 설계 원칙입니다.
- 구현 방법: 클래스와 필드를 final로 선언, 생성자에서 초기화, setter 제공 금지, 가변 객체에 대한 방어적 복사
- 장점: 스레드 안전성, 부작용 방지, 해시값 안정성, 캐싱 가능성
- 단점: 값 변경 시마다 새로운 객체 생성으로 인한 메모리 사용 및 성능 비용 증가
'면접 대비' 카테고리의 다른 글
[면접 대비] 가비지 컬렉션 알고리즘의 종류는? (0) | 2025.04.03 |
---|---|
[면접 대비] 로드 밸런싱이란 (0) | 2025.04.02 |
[면접 대비] 복합 인덱스 사용 시 주의사항은 무엇일까? (0) | 2025.03.31 |
[면접 대비] DB 커넥션 풀과 스레드 풀의 차이점은 무엇일까? (0) | 2025.03.26 |
[면접 대비] CPU 바운드와 IO 바운드의 차이점은 무엇일까? (1) | 2025.03.17 |