■ 자바의 안전성
자바는 C, C++에서 흔히 나타나는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류에서 안전하다. 하지만 다른 클래스로부터의 침범을 아무런 노력없이 막을 수 없다. 클라이언트가 여러분의 불변식을 꺠뜨리려 혈안이 되어 있다고 가정하고 방어저긍로 프로그래밍해야 한다.
■ 불변식을 지키지 못하는 클래스
아래 코드는 언뜻보면 불변식을 지킨 코드 처럼 보인다.
package ch8.hoon.item50;
import java.util.Date;
public 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 + " after " + end);
}
this.start = start;
this.end = end;
}
public Date start() { return start; }
public Date end() { return end; }
}
하지만 Date가 가변이라는 사실을 이용하면 그 불변식을 쉽게 깰 수 있다.
import java.util.Date;
public class PeriodTest {
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
System.out.println(period.start());
start.setTime(10);
System.out.println(period.start());
}
}
start의 값을 변경하고 period.start() 메서드 호출 시 결과가 바뀐 것을 확인할 수 있다.
자바8 이후로는 Date 대신 불변인 Instant를 사용하면 된다. Date는 낡은 API이니 새로운 코드를 작성할 때는 더이상 사용하면 안된다.
또한 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + " after " + end);
}
}
}
방어적 복사에 Date의 clone 메서드를 사용하지 않은 점에도 주목하자. Date는 final이 아니므로 clone이 Date에서 정의한게 아닐 수도 있다. 즉, clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수 있다.
매개 변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안된다.
Period 인스턴스의 속성을 다음과 같이 바꿀 수도 있다.
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
p.end().setYear(78); // p의 내부를 변경한다.
따라서 접근자에서도 필드의 방어적 복사본을 반환하는게 좋다.
public Date start() {
return new Dte(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
■ 핵심 정리
- 클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다.
- 복사 비용이 너무 크거나 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면 방어적 복사를 수행하는 대신 해당 구성 요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시하자.
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템52 다중정의는 신중히 사용해라 (0) | 2021.07.04 |
---|---|
[Effective Java] 아이템51 메서드 시그니처를 신중히 설계하라 (0) | 2021.07.04 |
[Effective Java] 아이템49 매개변수가 유효한지 검사하라 (0) | 2021.07.04 |
[Effective Java] 아이템48 스트림 병렬화는 주의해서 적용하라 (0) | 2021.06.29 |
[Effective Java] 아이템47 반환 타입으로는 스트림보다 컬렉션이 낫다 (0) | 2021.06.29 |