요약
방어적 복사를 해야하는 경우와 할 필요가 없는 경우는 다음과 같다.
방어적 복사를 해야하는 경우
- 클라이언트로 반환하거나 클라이언트로 받는 구성요소가 가변일 때
- 객체의 잠재적 변경 가능성이 있고, 변경이 되면 안되는 경우
방어적 복사를 할 필요가 없는 경우
- 성능 저하가 예상되는 경우 (ex. 매개변수 복사 비용이 너무 클 때)
- 클라이언트가 구성요소를 수정할 일이 없다는 신뢰가 있는 경우
- 불변식이 깨지더라도 그 영향이 호출한 클라이언트로 국한되는 경우 (ex. 래퍼 클래스)
→ 이런 경우에는 구성요소에 대한 변경의 책임이 클라이언트에 있음을 문서화 하자
불변식을 지키는 코드 작성
코드를 작성할 때, 외부 클라이언트가 내부 클래스를 수정하지 못하도록 보호하는 코드를 작성해야 한다. 이는 곧 클래스의 캡슐화를 구현하는 방법이다.
불변식을 지키지 못한 예시
public class Period1 {
private final Date start;
private final Date end;
public Period1(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(
start + "가 " + end + "보다 늦다.");
}
this.start = start;
this.end = end;
}
public Date start() { return start; }
public Date end() { return end; }
}
// Period 인스턴스 내부 공격
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // period 인스턴스 값 수정
- 언뜻 보기에는 불변식이 지켜질 것 같지만, 실제로는 Date가 가변 객체이기 때문에 외부에서 start, end를 수정할 수 있어 불변식을 깨는 코드이다.
- 따라서 Date 대신 Instant, LocalDateTime, ZonedDateTime을 사용해야 한다.
새로운 코드는 Date 외 다른 객체를 사용하면 되지만, 예전에 작성된 낡은 코드에 Date가 있을 때에는 방어적 복사로 대처할 수 있다.
방어적 복사
외부 공격으로부터 인스턴스 내부를 보호하기 위해서는 생성자에서 받은 가변 매개변수 각각을 방어적 복사 해야 한다.
예시1 - 생성자 방어적 복사
public Period2(Date start, Date end) {
// 1. 방어적 복사
this.start = new Date(start.getTime()); // 매개변수 start 방어적 복사
this.end = new Date(end.getTime()); // 매개변수 end 방어적 복사
// 2. 매개변수의 유효성 검사
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(
start + "가 " + end + "보다 늦다.");
}
}
방어적 복사 시 주의할 점
- 순서
- 방어적 복사본을 먼저 만든다.
- 매개변수의 유효성 검사 수행 (아이템49)
- clone()
- 매개변수가 제3자에 의해 확장될 수 있는 타입일 경우, 방어적 복사본을 만들 때 clone() 메서드를 사용하면 안된다
- 다른 곳에서 정의된 clone()일 경우, 악의를 가진 하위 클래스의 인스턴스가 반환될 가능성이 있기 때문이다
예시2 - 접근자 방어적 복사
예시1을 통해 생성자에 대한 공격은 예방했지만, 아직 접근자는 내부 가변 정보(Date객체)를 직접 반환하기 때문에 Period2 인스턴스는 변경가능하다.
public Date start() { return new Date(start.getTime()); }
public Date end() {return new Date(end.getTime()); }
- 직접 가변 필드(start, end)를 반환하는 것이 아닌 가변 필드의 방어적 복사본을 반환하면 Period2 인스턴스를 변경하는 방법은 없다.
- 즉 모든 필드가 객체 안에 완벽하게 캡슐화 되었다
방어적 복사가 필요한 경우
- 불변객체를 만들어야 하는 경우
- 객체의 잠재적 변경 가능성이 있고, 변경이 되면 안되는 경우
- 객체의 잠재적 변경 가능성이 있고 변경이 되어도 문제 없다면 방어적 복사를 꼭 하지 않아도 된다
- 가변인 내부 객체를 클라이언트에 반환 하는 경우
- 원본을 노출하지 말고 방어적 복사본을 반환함으로써 혹시 모를 원본 객체 변경 상황을 예방할 수 있다
- 길이가 1이상인 배열은 무조건 가변이기에 배열은 항상 방아적 복사를 하거나 배열의 불변 뷰를 반환해야 한다
코틀린 → 불변, Null 관련 해서 잘 되어 있음
'Dev Language > EffectiveJava' 카테고리의 다른 글
[EffectiveJava] 가변인수는 신중히 사용하라 (0) | 2025.03.23 |
---|---|
[EffectiveJava] 다중정의는 신중히 사용하라 (0) | 2025.03.16 |
[EffectiveJava] 스트림 병렬화는 주의해서 적용하라 (0) | 2025.03.03 |
[EffectiveJava] 스트림에서는 부작용 없는 함수를 사용하라 (0) | 2025.02.23 |
[EffectiveJava] 익명 클래스보다는 람다를 사용하라 (2) | 2025.02.16 |