티스토리 뷰

JAVA

방어적 복사

kkoon9 2022. 5. 1. 22:38

생성자를 통해 초기화 할 때, 새로운 객체로 감싸서 복사해주는 방법입니다.

외부와 내부에서 주소값을 공유하는 인스턴스의 관계를 끊어주기 위함입니다.

(예를 들어 외부에서 add했는데, 내부에 add가 반영되는 경우)

public School(Student[] students) {
    this.school = new ArrayList<>(students);
}

방어적 복사 이용

만약 생성자에서 유효성 검증이 필요하다고 합시다.

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

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(start, end)) {
        throw new IllegalArgumentException("");
    }
}

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

하지만 생성자에서만 복사본을 만든다고 내부 값을 변경할 수 없는 것은 아닙니다.

getter로 얻은 객체를 통해 얼마든지 내부 값이 변경될 수 있습니다.

// Before
List<School> schoolList = school.getList();
schoolList.add();
// Before
period.getStart().setTime();

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

// After
public List<School> toList() {
    return Collections.unModifiableList(school);
}
// After
public Date getStart() {
    return new Date(start.getTime());
}

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

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

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

참고 자료

 

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

생성자를 통해 초기화 할 때, 새로운 객체로 감싸서 복사해주는 방법이다.만약 생성자에서 유효성 검증이 필요하다고 하자. 일반적으로는 아래와 같이 넘어온 값을 검증하고 객체 내부변수에

velog.io

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함