불변객체를 공부하다보니 방어적 복사라는 키워드가 자주등장하여 따로 포스팅 하게 되었다.
불변객체(Immutable Object)를 사용해야하는 이유
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 중 하나를 사용하여 값을 리턴하는것이 적절하다.
'JAVA' 카테고리의 다른 글
일급 컬렉션 (First Class Collection) 을 사용하자 (0) | 2022.03.01 |
---|---|
String , StringBuilder, StringBuffer 속도차이 비교 (0) | 2022.02.09 |
Equals 와 HashCode (0) | 2022.02.09 |
자바의 예외구분 Checked Exception, Unchecked Exception (0) | 2022.02.08 |
불변객체(Immutable Object)를 사용해야하는 이유 (0) | 2022.02.08 |
상속보다는 컴포지션을 사용하자 (4) | 2022.02.05 |