우선 우리는 불변 객체를 사용하는 것을 권장한다. 불변 객체란 객체 생성 이후 내부의 상태가 변하지 않는 객체를 말한다. 만약, 객체의 내부 상태를 제공하는 메소드를 지원하는 경우 방어적 복사를 통해 제공한다. 그렇다면, 방어적 복사란 무엇일까?
방어적 복사란?
내부의 상태를 제공하는 메소드를 지원하는 경우 (ex. getter) 외부와 내부에서 주소값을 공유하는 인스턴스의 관계를 끊어주기 위해 사용된다. 우선, 아래의 예시 코드를 보자.
public class TestClass{
private final List<String> strings;
public TestClass() {
strings = new ArrayList<>();
}
public List<String> getStringsWithoutDefensiveCopy() {
return strings;
}
public List<String> getStringsWithDefensiveCopy() {
return new ArrayList<>(strings);
}
}
외부에서 getStringsWithoutDefensiveCopy 메서드를 사용해 TestClass의 필드인 strings를 가져온 뒤, add를 수행하면 TestClass의 필드인 strings에서도 함께 add가 된다. 만약 방어적 복사를 사용해 리턴한 strings를 이용해 add를 수행한다면, TestClass의 필드인 strings에는 아무 영향이 없다. (주소값을 공유하는 관계가 끊어졌기 때문이다.)
하지만 List가 아닌, Stack 자료 구조의 경우에는 방어적 복사를 할 필요가 없다.
왜 그럴까?
Stack과 방어적 복사, 그리고 Deque?
Stack은 연산 자체가 synchronized 로 되어있다. 우리가 방어적 복사를 이용하는 이유는 동기화 이슈로 인해 방어적 복사를 수행하게 되는데, 자료구조의 특성 자체가 모든 연산에 있어 동기화가 보장된다면 과연 방어적 복사를 하는 의미가 있을까? (그리고 어떻게 해야 방어적 복사가 가능한지도 모르겠다😅 return new Stack<>(st); 은 불가능하다.)
그리고 이러한 이유도 있겠지만 더불어 Stack보다는 Deque를 사용하라고 권장한다. 그 이유는 Deque는 스택과 달리 동기화가 필요하지 않은 부분에 있어서는 동기화를 지원하지 않는다. (말이 이상한데.. 아무튼 동기화를 무조건적으로 보장하는 것은 아니다.) 따라서 동기화가 필요하지 않은 부분에서 stack을 사용한다면, 동기화 비용이 계속적으로 들기 때문에 Deque를 사용하여 성능상 Stack에 비해 Deque가 더 낫다고 한다. 동기화 연산은 비용이 비싸기 때문에 동기화를 필요로하지 않는 부분에 대해서는 lock을 점유하지 않는 것이 성능상 더 좋다고 할 수 있다!
따라서 요약하자면, 방어적 복사는 동기화를 방지하기 위해 수행하는데, 스택은 애초에 자료구조 자체에서 모든 연산이 동기화 되도록 구현되어있기 때문에 방어적 복사를 하는 의미가 없다. 따라서 스택보다는 Deque의 사용을 권장한다! 이다.