자바는 안전한 언어다. 자바로 작성한 클래스는 시스템의 다른 부분에서 무슨 짓을 하든 그 불변식이 지켜진다. 메모리 전체를 하나의 거대한 배열로 다루는 언어에서는 누릴 수 없는 강점이다.

하지만 아무리 자바라 해도 다른 클래스로부터의 침범을 아무런 노력 없이 다 막을 수 있는 건 아니다. 그러니 클라이언트가 여러분의 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다. 실제로도 악의적인 의도를 가진 사람들이 시스템의 보안을 뚫으려는 시도가 늘고 있다. 평범한 프로그래머도 순전히 실수로 여러분의 클래스를 오작동하게 만들 수 있다. 물론 후자의 상황이 더 흔하다. 어떤 경우든 적절치 않은 클라이언트로부터 클래스를 보호하는 데 충분한 시간을 투자하는 게 좋다.

어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하다. 하지만 주의를 기울이지 않으면 자기도 모르게 내부를 수정하도록 허락하는 경우가 생긴다. 흔히 발생하는 문제다. 예컨대 기간(period)을 표현하는 다음 클래스는 한번 값이 정해지면 변하지 않도록 할 생각이었다.

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;
		}

		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 대신 불변(아이템 17)인 Instant 를 사용하면 된다. Date 는 낡은 API 이니 새로운 코드를 작성할 때는 더 이상 사용하면 안된다. 하지만 앞으로 쓰지 않는다고 이 문제에서 해방되는 건 아니다. Date 처럼 가변인 낡은 값 타입을 사용하던 시절이 워낙 길었던 탓에 여전히 많은 API 와 내부 구현에 그 잔재가 남아 있다. 이번 아이템은 예전에 작성된 낡은 코드들을 대처하기 위한 것이다.

외부 공격으로부터 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사(defensive copy)해야 한다. Period 인스턴스 안에서는 원본이 아닌 복사본을 사용한다.

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 IllegalArgumentExcetion(this.start + "가 " + this.end + "보다 늦다.");
}

새로 작성한 생성자를 사용하면 앞서의 공격은 더 이상 Period 에 위협이 되지 않는다. 매개변수의 유효성을 검사(아이템 49)하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목하자. 멀티스레드 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있다. 방어적 복사를 매개변수 유효성 검사 전에 수행하면 이런 위험에서 해방될 수 있다.

방어적 복사에 Date 의 clone 메서드를 사용하지 않은 점에도 주목하자. Date 는 final 이 아니므로 clone 이 Date 가 정의한 게 아닐 수 있다. 즉, clone 이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있다. 예컨대 이 하위 클래스는 start 와 end 필드의 참조를 private 정적 리스트에 담아뒀다가 공격자에게 이 리스트에 접근하는 길을 열어줄 수도 있다. 이런 공격을 막기 위해서는 매개변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone 을 사용해서는 안된다.

생성자를 수정하면 앞서의 공격은 막아낼 수 있지만, Period 인스턴스는 아직도 변경 가능하다. 접근자 메서드가 내부의 가변 정보를 직접 드러내기 때문이다.

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());
}

새로운 접근자까지 갖추면 Period 는 완벽한 불변으로 거듭난다. Period 자신 말고는 가변 필드에 접근할 방법이 없다. 모든 필드가 객체 안에 완벽하게 캡슐화 되었다.

생성자와 달리 접근자 메서드에서는 방어적 복사에 clone 을 사용해도 된다. Period 가 가지고 있는 Date 객체는 java.util.Date 임이 확실하기 때문이다. (신뢰할 수 없는 하위 클래스가 아니다) 그렇더라도 아이템 13에서 설명한 이유 때문에 인스턴스를 복사하는 데는 일반적으로 생성자나 정적 팩터리를 쓰는게 좋다.

매개변수를 방어적으로 복사하는 목적이 불변 객체 를 만들기 위해서만은 아니다. 메서드든 생성자든 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경될 수 있는지를 생각해봐야 한다. 변경될 수 있는 객체라면 그 객체가 클래스에 넘겨진 뒤 임의로 변경되어도 그 클래스가 문제없이 동작할지를 따져보라. 확신할 수 없다면 복사본을 만들어 저장해야 한다. 예컨대 클라이언트가 건네준 객체를 내부의 Set 인스턴스에 저장하거나 Map 인스턴스의 키로 사용한다면, 추후 그 객체가 변경될 경우 객체를 담고 있는 Set 혹은 Map 의 불변식이 깨질 것이다.

내부 객체를 클라이언트에 건네주기 전에 방어적 복사본을 만드는 이유도 마찬가지다. 여러분의 클래스가 불변이든 가변이든, 가변인 내부 객체를 클라이언트에 반환할 때는 반드시 심사숙고해야 한다. 안심할 수 없다면 방어적 복사본을 반환해야 한다. 길이가 1 이상인 배열은 무조건 가변임을 잊지 말자. 그러니 내부에서 사용하는 배열을 클라이언트에 반환할 때는 항상 방어적 복사를 수행해야 한다. 혹은 배열의 불변 뷰를 반환하는 대안도 있다. 두 방법 모두 아이템 15에 소개되었다.

이상의 모든 작업에서 우리는 되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다 는 교훈을 얻을 수 있다.(아이템 17)