본문 바로가기

JAVA

방어적 복사 vs Unmodifiable Collection

불변객체를 공부하다보니 방어적 복사라는 키워드가 자주등장하여 따로 포스팅 하게 되었다.

 

불변객체(Immutable Object)를 사용해야하는 이유

 

불변객체(Immutable Object)를 사용해야하는 이유

1. 불변객체(Immutable Object)란? 불변객체는 말그대로 변하지않는 객체로 객체가 생성된후 내부 상태가 변하지 않는 객체를 의미한다. 불변객체는 Setter 메소드를 제공하지 않으며, 내부상태를 제공

dev-cool.tistory.com


1. 방어적 복사란


생성자의 인자로 가변 객체를 받와 내부 필드를 초기화 하거나, getter 메서드에서 내부 객체를 반환할 때, 객체의 복사본을 만들어 반환하는 것이다.

 

방어적 복사를 하게되면 외부에서 가변 객체를 변경하여도 인스턴스 내의 가변객체는 변경되지 않는다.

 

우선 방어적 복사를 사용하지 않았을때 코드를 살펴보자.

 

사용자의 이름 정보를 가지고있는 Member 클래스,

VIP 고객의 이름 List를 담아두는 VIPMembers 클래스를 만들어보자

 

public class Member {
    private final String name;

    public Member(String name) {
        this.name = name;
    }
}

 

public class VIPMembers {
    private final List<Member> members;

    public VIPMembers(List<Member> members) {
        this.members = members;
    }
}

 

public class Application {
    public static void main(String[] args) {
        List<Member> memberList = new ArrayList<>();
        memberList.add(new Member("김씨"));
        memberList.add(new Member("쿨씨"));

        VIPMembers vipMembers = new VIPMembers(memberList);
        memberList.add(new Member("아저씨"));
    }
}

 

Member 타입 List 를 하나 생성하고, 2개의 Member 객체를 추가한다.

VIPMembers 생성자에 인자로 사용하여 초기화한 후 Member 타입 List 에 다른 Member 객체를 추가한다.

 

결과를 예상해보면 VIPMember 클래스는 내부 필드를 변경하는 메소드도 제공하지 않고 private final 로 선언하여 접근이 불가능하게 만들었지만

 

가변객체인 Member 타입 List 를 수정하면 VIPMember 내부 필드의 값도 같이 변경되는걸 예상할 수 있다.

 

그 이유를 살펴보기위해 디버깅 해보면

main 에서 생성한 memberList 객체와 vipMembers의 내부 필드 객체인 members의 참조값이 일치하는것을 확인할 수 있다.

 

이번엔 방어적 복사를 하여 다시 디버깅해보면

 

public class Member {
    private final String name;

    public Member(String name) {
        this.name = name;
    }
}

 

public class VIPMembers {
    private final List<Member> members;

    public VIPMembers(List<Member> members) {
    	//new ArrayList 로 새로운 객체를 만들어 방어적 복사를 수행한다.
        this.members = new ArrayList<>(members);
    }
}

 

public class Application {
    public static void main(String[] args) {
        List<Member> memberList = new ArrayList<>();
        memberList.add(new Member("김씨"));
        memberList.add(new Member("쿨씨"));

        VIPMembers vipMembers = new VIPMembers(memberList);
        memberList.add(new Member("아저씨"));
    }
}

 

 

방어적 복사를 하고나니 memberList 객체의 요소가 변경되어도 vipMembers 객체 내부 필드의 members 객체는 변경되지 않는다.

 

서로 다른 주소값을 가지고 있기 때문이다.

 

2. 방어적 복사는 깊은 복사인가


결론부터 말하자면 깊은 복사가 아니다.

 

위의 디버깅 결과를 보면 memberList 객체의 0, 1번째 Member 객체와

vipMembers 객체의 0, 1번째 Member 객체의 참조값이 일치하는것을 확인할 수 있다.

 

만약 방어적 복사가 깊은 복사라면 원본과 복사본을 비교해 봤을때 모든 요소의 참조값이 달라야 하지만 그렇지 않은 것을 확인할 수 있다.

 

따라서 우리는 Member 클래스를 불변객체로 만들어서 사용하게된다면 불필요한 방어적 복사를 피할수 있으며, 외부로부터 변경에 취약하지 않도록 할 수 있다.

 

3. Unmodifiable Collection 이란


Unmodifiable Collection 을 사용하면 외부에서 값을 변경시 예외처리되므로 안전하게 보장할 수 있다.

 

위의 코드중 VIPMembers 만 수정해보면

 

public class VIPMembers {
    private final List<Member> members;
	
    //Collection.unmodifiableList() 를 사용한 복사
    public VIPMembers(List<Member> members) {
        this.members = Collections.unmodifiableList(members);
    }
}

 

디버깅 결과

 

사용법은 코드를 참조하면 되며, unmodifiableList() 메소드를 통해서 리턴되는 리스트는 읽이 용도로만 사용가능하며, set(), add() 등 리스트에 변경을 주는 메소드를 호출하면 UnsupportedOperationException이 발생한다.

 

4. 방어적복사 or Unmodifiable Collection


결국 이 포스팅의 핵심은 방어적 복사를 사용하는것은 객체의 내부 필드의 값을 외부로부터 보호하는것이 목적이라고 할 수 있다.

 

1. 생성자의 인자로 객체를 받았을 때

 

외부에서 넘겨줬던 객체를 변경해도 내부 객체는 변하지 않아야 하므로 방어적 복사가 적절하다.

 

2. getter 를 통해 객체를 리턴할 때

 

이러한 상황에선 방어적복사Unmodifiable Collection 중 하나를 사용하여 값을 리턴하는것이 적절하다.