[Java] 방어적 복사(Defensive copy)

Junseo Kim·2021년 2월 11일
6

[Java]자바 기초

목록 보기
29/35

방어적 복사란?

생성자를 통해 초기화 할 때, 새로운 객체로 감싸서 복사해주는 방법이다. 외부와 내부에서 주소값을 공유하는 인스턴스의 관계를 끊어주기 위함이다.(예를 들어 외부에서 add했는데, 내부에 add가 반영되는 경우)

public Cars(Racers racers) {
    this.cars = new ArrayList<>(generateCars(racers)); // 외부와의 관계 끊기
}

방어적 복사 이용

만약 생성자에서 유효성 검증이 필요하다고 하자. 일반적으로는 아래와 같이 넘어온 값을 검증하고 객체 내부변수에 검증된 값을 할당해주는 순서가 맞다고 생각할 수 있다.

public Period(Date start, Date end) {
    if (validation(start, end)) {
        throw new IllegalArgumentException("");
    }
    this.start = start;
    this.end = end;
}

하지만 Date같은 경우는 값이 변경될 수 있으므로 생성자에서 매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본을 이용해서 유효성을 검증한다. 만약 멀티쓰레딩 환경일 경우 유효성 검사부터 후 복사본을 만드는 사이에 원본 값이 변경될 위험이 있기 때문이다.

public Period(Date start, Date end) {
    this.start = new Date(start.getTime()); // 방어적 복사
    this.end = new Date(end.getTime()); // 방어적 복사
    if (validation(this.start, this.end)) {
        throw new IllegalArgumentException("");
    }

}

clone을 사용해서 본사본을 만들 수도 있지만, 생성자의 매개변수가 final 클래스가 아니어서 확장될 수 있는 타입이면 방어적 복사본을 만들때 clone을 사용하면 안된다.(재정의된 clone이 호출될 수 있기 때문)

하지만 생성자에서만 복사본을 만든다고 내부 값을 변경할 수 없는 것은 아니다.
getter로 얻은 객체를 통해 얼마든지 내부 값이 변경될 수 있다.

List<Car> carList = cars.getList();
carList.add(); // 값이 변경될 수 있다.
period.getStart().setTime(); // getter를 통해 가져온 값으로 내부 값 변경 가능

따라서 getter에서도 복사본을 리턴해줘야한다.

public List<Car> toList() {
    return Collections.unmodifiableList(cars);
}
public Date getStart() {
    return new Date(start.getTime());
}

생성자의 매개변수로 프리미티브 타입이 넘어온다면 애초에 값이 복사되어 전달되므로 유효성검사 후 그냥 할당해줘도 되지만, 생성자의 매개변수로 객체가 넘어온다면 먼저 복사본을 만든 후 유효성 검사를 해주는 것이 좋다.

즉, 불변성이 유지된다면 검증부터 해줘도 되지만 그걸 보장할 수 없다면 복사본을 만든 후, 복사본으로 검증해주는 것이 좋다.

또 getter도 복사본을 리턴해줘야 완벽한 캡슐화라고 볼 수 있다.

4개의 댓글

comment-user-thumbnail
2021년 12월 8일

뭐야 왜 또 너야

1개의 답글
comment-user-thumbnail
2022년 2월 6일

혹시 질문드려도 될까요?
1. 방어적 복사가 적용된 Period 클래스에서 검증함수 매개변수에 this.start, this.end가 들어가야하는 것이 아닌지 궁금합니다.

  1. 방어적 복사가 진행되어 복사된 맴버변수로 검증이 진행되었을때 (this.start, this.end를 변경할 수 있는) 다른 멤버 메서드가 this.start, this.end 를 변경하게 되는 경우는 방어하지 못하지 않나요?(멀티스레드 환경에서)
1개의 답글