반응형
이 포스팅에서 작성하는 내용은 EFFECTIVE JAVA(이펙티브자바) 에서 발췌하였습니다.
아이템 50. 적시에 방어적 복사본을 만들라
클라이언트가 객체의 불변식을 깨뜨리지 못하도록 프로그래밍을 해야한다.
어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하지만, 자칫 잘못하면 내부를 수정하도록 허락하는 경우가 생긴다.
ex 1) 기간을 표현하는 클래스 (불변을 지키지 못함)
// 기간을 표현하는 클래스
public final class Period {
private final Date start;
private final Date end;
/**
* @param start 시작 시각
* @param end 종료 시각. 시작 시각보다 뒤여야 한다.
* @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다.
* @throws NullPointerException start나 end가 null이면 발생한다.
*/
public Period(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;
}
public String toString() {
return start + " - " + end;
}
}
- 위의 코드에서 Date가 가변이라는 사실을 이용하면 불변식을 깨트릴 수 있다.
ex 2) Date 가 가변임을 이용한 Period 인스턴스 내부 공격
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 변경했다!
- Date 대신 불변인 인스턴트를 사용하면 된다.
- Date 는 오래된 API이니 새로운 코드를 작성할 때는 더 이상 사용하지 말자.
방어적 복사
- 외부 공격으로부터 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
ex 3) 매개변수의 방어적 복사본을 만들어 사용하는 생성자 및 접근자
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(
this.start + "가 " + this.end + "보다 늦은 시각입니다.");
}
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
- 매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사했다.
- 멀티스레딩 환경이라면 원본 객체의 유효성을 검사하고 복사본을 만드는 찰나에 다른 스레드가 원본 객체를 수정할 수 있기 때문이다.
- Date의 Clone 메서드를 사용하지 않은 이유
- Date는 final 이 아니므로 clone이 Date가 정의한 게 아닐 수 있다.
- 즉, clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있다.
- 매개변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone 을 사용하면 안된다.
방어적 복사의 유의점
- 방어적 복사에는 성능 저하가 따르고, 항상 쓸 수 있는 것도 아니다.
- 호출자가 컴포넌트 내부를 수정하지 않으리라 확신하면 방어적 복사를 생략할 수 있다.
- 다른 패키지에서 사용한다고 해서 넘겨받은 가변 매개변수를 항상 방어적으로 복사해 저장해야 하는 것은 아니다.
- 방어적 복사를 생략해도 되는 상황은 해당 클래스와 그 클라이언트가 상호 신뢰할 수 있을 때, 혹은 불변식이 깨지더라도 그 영향이 호출한 클라이언트로 국한될 때이다.
정리
- 클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다.
- 복사 비용이 너무 크거나 클라이언트가 그 요소를 수정할 일어 없음을 신뢰한다면, 방어적 복사를 수행하는 대신 해당 구성 요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시하자.
반응형
'BE > Java' 카테고리의 다른 글
[Effective Java] 아이템 52. 다중정의는 신중히 사용하라 (0) | 2022.11.15 |
---|---|
[Effective Java] 아이템 51. 메서드 시그니처를 신중히 설계하라 (0) | 2022.11.14 |
[Effective Java] 아이템 49. 매개변수가 유효한지 검사하라 (0) | 2022.11.10 |
[Effective Java] 아이템 48. 스트림 병렬화는 주의해서 적용하라 (0) | 2022.11.09 |
[Effective Java] 아이템 47. 반환 타입으로는 스트림보다 컬렉션이 낫다. (0) | 2022.11.08 |