자바는 안전한 언어지만 다른 클래스로부터의 침범을 아무런 노력 없이 다 막을 수 있지 않다.
클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다.
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(start + "가 " + end + "보다 늦다.");
this.start = start; //넘어온 날짜 데이터를 그대로 넣어주고 있음
this.end = end; //넘어온 날짜 데이터를 그대로 넣어주고 있음
}
//this.start & this.end 반환
public Date start() {
return start;
}
public Date end() {
return end;
}
public String toString() {
return start + " - " + end;
}
}
얼핏 불변처럼 보이지만 Date은 가변이다.
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
System.out.println(p); //Sun May 08 13:27:13 KST 2022 - Sun May 08 13:27:13 KST 2022
end.setYear(78); // p의 내부를 변경했다!
System.out.println(p); //Sun May 08 13:27:13 KST 2022 - Mon May 08 13:27:13 KST 1978
이를 해결하기 위해서는 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
위 예제의 생성자를 아래와 같이 변경하면
// 매개변수의 방어적 복사본
public PeriodClone(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 + "보다 늦다.");
}
이제 안바뀌겠지?0?
Date start = new Date();
Date end = new Date();
PeriodClone p = new PeriodClone(start, end);
System.out.println(p); //Sun May 08 13:40:37 KST 2022 - Sun May 08 13:40:37 KST 2022
end.setYear(78);
System.out.println(p); //Sun May 08 13:40:37 KST 2022 - Sun May 08 13:40:37 KST 2022
p.end().setYear(78); // p의 내부를 변경했다!
System.out.println(p); //Sun May 08 13:41:28 KST 2022 - Mon May 08 13:41:28 KST 1978
가변 데이터인 end 데이터를 변경할때는 생성자에 방어적 본사본을 만들었기 때문에 변경되지 않음을 확인했으나 end 게터 함수에서 return end
를 하고 있기 때문에 다시 변경이 된다.
이를 해결하기 위해서는..
start와 end 필드에 방어적 복사본을 반환하도록 처리하면 해결된다.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
이러면 end() 호출 후 setYear()로 변경한다고 해도 p 내부 값은 변경할 수 없다.
가변인 내부 객체를 클라이언트에 반환하는 방법 두가지는 (item-15)에서 확인
클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다. 복사 비용이 너무 크거나 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사를 수행하는 대신 해당 구성요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시하도록 하자.