[아이템 50] 적시에 방어적 복사본을 만들어라

Jimin Lim·2023년 7월 3일
0

Effective Java

목록 보기
33/38
post-thumbnail
post-custom-banner

아이템 50

적시에 방어적 복사본을 만들어라

✅ Java

자바는 네이티브 메서드를 사용하지 않아 C, C++에서 흔히 보는 버퍼 오버런, 배열 오버런, 와일드 포인터와 같은 메모리 충돌 오류에서 안전하다. 자바로 작성한 클래스는 시스템의 다른 부분에서 무얼하든 불변식이 지켜진다.

  • 네이티브 메서드(Native Method): C, C++와 같은 네이티브 프로그래밍 언어로 작성한 메서드

✅ 불변식을 깨뜨리는 예

클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍 해야 한다.

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 start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setYear(78); // period 내부 변경 성공!

Java는 CallByValue로, 참조형 변수를 넘긴다면 같은 객체를 바라보기에 가변인 객체를 넘기면 실제로 영향을 미친다. 자바8 이후 불변인 객체, 혹은 LocalDateTime이나 ZonedDateTime 사용하면 해결할 수 있다.

참고: https://bcp0109.tistory.com/360

✅ 방어적 복사

외부 공격으로부터 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 IllegalArgumentException(
				this.start + "가 " + this.end + "보다 늦다."
			);
		}
	}

먼저 방어적 복사를 한 후, 복사본으로 유효성을 검사하고 있다. 멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후, 복사본을 만드려고 하는 찰나 다른 스레드가 원본 객체를 수정할 위험이 있기때문이다.

위 방식은 여전히 접근자 메서드가 가변 정보를 직접 드러내기에 변경이 가능하다.

public Date start() {
	return new Date(start.getTime);
}

public Date end() {
	return new Date(end.getTime);
}

따라서 최종적으로 접근자도 방어적 복사본을 반환하도록 작성할 수 있다.

✅ 결론

길이가 1 이상인 배열은 무조건 가변임을 잊지말고, 내부에서 사용하는 배열을 클라이언트에게 반환할 때는 항상 방어적 복사를 수행해야 한다. 되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다.

앞선 예에서 Date를 사용한다면, 자바8에서는 LocalDateTime이나 ZonedDateTime을 사용하는 방법이 있겠지만 이전에는 long 정수를 넘기는 방법이 있다.

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️
post-custom-banner

0개의 댓글