클라이언트가 우리의 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다.
주의를 기울이지 않으면 자기도 모르게 내부를 수정하도록 허락하는 경우가 생긴다.
Date는 낡은 API이니 새로운 코드를 작성할 때는 더 이상 사용하면 안 된다.
- Date가 가변이라는 사실을 이용해서 불변식을 깬다.
-
생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
-
매개변수의 유효성을 검사(item 49)하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사하자.
- 멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다.
-
매개변수가 제3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안 된다.
- clone이 다른 곳에서 정의한 것일 수도 있다.
-
접근자 메서드는 가변 필드의 방어적 복사본을 반환하자.
public Date start() { return new Date(start.getTime()); }
-
생성자와 달리 접근자 메서드에서는 방어적 복사에 clone을 사용해도 된다.
- Period가 가지고 있는 Date 객체는 java.util.Date임이 확실하기 때문이다.
- 그렇더라도 item 13에서 설명한 이유 때문에 인스턴스를 복사하는 데는 일반적으로 생성자나 정적 팩터리를 쓰는게 좋다.
매개변수를 방어적으로 복사하는 목적이 불변 객체를 만들기 위해서만은 아니다.
메서드든 생성자든 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경될 수 있는지를 생각해야 한다.
- 변경될 수 있는 객체라면 그 객체가 클래스에 넘겨진 뒤 임의로 변경되어도 그 클래스가 문제없이 동작할지를 따져보라.
- 확신할 수 없다면 복사본을 만들어 저장해야 한다.
- 클라이언트가 건네준 객체를 내부의 Set 인스턴스에 저장하거나 Map 인스턴스의 키로 사용한다면, 추후 그 객체가 변경될 경우 객체를 담고 있는 Set 혹은 Map의 불변식이 깨질 것이다.
클래스가 불변이든 가변이든, 가변인 내부 객체를 클라이언트에 반환할 때는 반드시 심사숙고해야 한다.
내부에서 사용하는 배열을 클라이언트에 반환할 때는 항상 방어적 복사를 수행해야 한다. 혹은 배열의 불변 뷰를 반환하라.(item 15)
되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다.
방어적 복사에는 성능 저하가 따르고, 항상 쓸 수 있는 것도 아니다(같은 패키지에 속하는 등의 이유로).
다른 패키지에서 사용한다고 해서 넘겨받은 가변 매개변수를 항상 방어적으로 복사해 저장해야 하는 것은 아니다.
- 때로는 메서드나 생성자의 매개변수로 넘기는 행위가 그 객체의 통제권을 명백히 이전함을 뜻하기도 한다.
- 이처럼 통제권을 이전하는 메서드를 호출하는 클라이언트는 해당 객체를 더 이상 직접 수정하는 일이 없다고 약속해야 한다.
- 관련 메서드나 생성자에 그 사실을 확실히 문서에 기재해야 한다.
- 해당 클래스와 그 클라이언트가 상호 신뢰할 수 있을 때
- 불변식이 깨지더라도 그 영향이 오직 호출한 클라이언트로 국한될 때
- 래퍼 클래스 패턴(itme 18)의 특성상 클라이언트는 래퍼에 넘긴 객체에 여전히 직접 접근할 수 있다.
- 따라서 래퍼의 불변식을 쉽게 파괴할 수 있지만 그 영향을 오직 클라이언트 자신만 받게 된다.