클라이언트가 우리의 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍을 해야 함
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 + " after " + end);
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
... // 나머지 코드 생략
}
Date가 가변이므로 불변식을 깨뜨릴 수 있음
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 수정했다!
자바8 이상이라면 Date 대신 불변 Instant(혹은 LocalDateTime, ZonedDateTime)를 사용할 수 있음
→ 모두 불변으로 설계되어있음
방어적 복사
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 + " after " + this.end);
}
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // p의 내부를 변경
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
접근자가 가변 필드의 방어적 복사본을 반환하게 하면 됨
생성자와 달리 접근자 메서드에서는 방어적 복사에 clone을 사용해도 됨 (Period가 가지고 있는 Date 객체는 java.util.Date 임이 확실하기 때문)
→ 하지만 일반적으로 생성자와 정적 팩터리를 쓰는 게 좋음 (아이템13 참고)