자바에서의 방어적 복사(Defensive Copy)와 깊은 복사의 중요성
🛡️ 방어적 복사란?
방어적 복사는 객체의 내부 상태가 외부로부터 변경되지 않도록 복사본을 제공하는 기법입니다. 이를 통해 객체의 불변성을 유지하고, 예기치 않은 사이드 이펙트를 방지할 수 있습니다.
📍 사용 시점
- 생성자에서의 방어적 복사: 외부에서 전달된 가변 객체를 내부에 저장할 때, 복사본을 만들어 저장합니다.
- Getter 메서드에서의 방어적 복사: 내부의 가변 객체를 외부에 반환할 때, 복사본을 반환합니다.
🔄 깊은 복사와 얕은 복사의 차이
구분 | 설명 | 예시 코드 |
---|---|---|
얕은 복사 | 객체의 참조만 복사하여 원본과 복사본이 같은 객체를 참조 | List<LottoNumber> copy = originalList; |
깊은 복사 | 객체의 실제 값을 복사하여 원본과 복사본이 독립적인 객체를 참조 | List<LottoNumber> copy = new ArrayList<>(originalList); |
주의: new ArrayList<>(originalList);
는 리스트 자체는 복사하지만, 리스트 안의 객체들은 동일한 참조를 가리키므로 완전한 깊은 복사가 아닙니다. 리스트 안의 객체들도 복사하려면 각 객체의 복사본을 생성해야 합니다.
🧪 예제 코드 분석
public class Lotto {
private final List<LottoNumber> numbers;
public Lotto(List<LottoNumber> numbers) {
validateSize(numbers);
this.numbers = new ArrayList<>(numbers); // 방어적 복사
}
private void validateSize(List<LottoNumber> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
}
}
🔍 문제점 분석
- 얕은 복사로 인한 불변성 위반 가능성
- 검증 시점의 문제 (복사 전에 검증이 이루어짐)
✅ 개선된 코드
public class Lotto {
private final List<LottoNumber> numbers;
public Lotto(List<LottoNumber> numbers) {
List<LottoNumber> defensiveCopy = new ArrayList<>();
for (LottoNumber number : numbers) {
defensiveCopy.add(new LottoNumber(number)); // 깊은 복사
}
validateSize(defensiveCopy);
this.numbers = defensiveCopy;
}
private void validateSize(List<LottoNumber> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
}
}
🔒 불변 컬렉션 사용
public List<LottoNumber> getNumbers() {
return Collections.unmodifiableList(numbers);
}
📌 결론
- 방어적 복사는 객체의 불변성과 캡슐화를 유지하기 위한 중요한 기법입니다.
- 깊은 복사와 얕은 복사의 차이를 이해하고, 상황에 맞게 적절한 복사 방법을 선택해야 합니다.
- 불변 컬렉션을 사용하여 외부에서의 변경을 방지할 수 있습니다.
댓글남기기