티스토리 뷰

방어적 복사에 대해서 공부하다가 얕은 복사, 깊은 복사, 방어적 복사 함께 비교하면 좋을거 같아서 비교글을 써보기로 했다.

 

얕은 복사 vs 깊은 복사 vs 방어적 복사

  • 얕은 복사: 객체의 참조만 복사한다. 즉, 원본 객체와 복사본 객체가 같은 메모리 주소를 참조하게 된다.
  • 깊은 복사: 객체와 내부에 포함된 참조 타입 필드(예: 배열, 필드)까지 모두 복사하여 원본과 독립적인 객체를 생성한다.
  • 방어적 복사: 외부에서 전달된 객체를 변경할 수 없도록 원본 객체의 상태를 보호하기 위해 내부에서 새로운 객체를 생성한다.

 

💡 얕은 복사

객체의 참조만 복사한다.

객체의 내부 데이터를 복사하지 않고, 그 데이터가 저장된 메모리 주소값(참조)을 복사한다는 걸 의미한다.

 

얕은 복사로 생성된 객체는 겉보기엔 원본 객체와 별도의 객체로 보이지만,

실제 내부 필드가 참조하는 데이터는 같은 객체를 가리키게 된다.

 

따라서 원본이나 복사본 중 하나의 내부 데이터를 변경하면, 다른 쪽도 함께 영향을 받는 문제가 발생한다.

 

 

언제 사용할까?

  • 메모리 절약이 중요한 경우
  • 원본 데이터가 변경되지 않는다는 보장이 있을 때
class Person {
    String name;
    int[] scores;

    Person(String name, int[] scores) {
        this.name = name;
        this.scores = scores;
    }
}

// 얕은 복사 사용
Person original = new Person("coding", new int[]{90, 85});
Person shallowCopy = original; // 얕은 복사

// 동일한 배열을 참조 테스트
System.out.println(original.name); // coding
System.out.println(shallowCopy.name); // coding
System.out.println(original.scores == shallowCopy.scores); // true

// 복사본의 값을 바꾸면 원본의 값이 변한다.
shallowCopy.scores[0] = 100; // 복사본 배열 수정
System.out.println(Arrays.toString(original.scores)); // [100, 85] 원본 배열이 변했다!

originalshallowCopy의 필드 scores는 동일한 배열을 참조한다.
따라서 둘 중에 하나를 변경하면 모두 영향을 받는다.

 

 

💡 깊은 복사

깊은 복사는 객체와 내부 필드(특히 참조 타입 필드)까지 복사한다.

 

 

언제 사용할까?

  • 원본 데이터와 복사본을 완전히 분리해야 하는 경우
  • 원본 변경이 복사본에 영향을 미치면 안 되는 상황
class Person {
    private String name;
    private int[] scores; // 참조 타입인 배열 필드

    // 생성자
    Person(String name, int[] scores) {
        this.name = name;
        this.scores = scores;
    }

    // 깊은 복사를 위한 생성자
    Person(Person other) { // 복사 생성자
        this.name = other.name; // String은 불변 객체라 값 복사도 괜찮다
        this.scores = other.scores.clone(); // clone()을 호출해서 참조 타입은 깊은 복사
    }

    public int[] getScores() {
        return scores;
    }

    public String getName() {
        return name;
    }
}

깊은 복사를 해보자.

Person original = new Person("coding", new int[]{90, 85, 80});

// 깊은 복사 생성자 호출해서 새로운 객체를 생성한다.
Person deepCopy = new Person(original);

// 복사본의 배열 수정
deepCopy.getScores()[0] = 100;

// 원본의 배열과 복사본의 배열이 독립적이다.
System.out.println(Arrays.toString(original.getScores())); // [90, 85, 80] 원본 유지
System.out.println(Arrays.toString(deepCopy.getScores())); // [100, 85, 80] 복사본만 변경됨

original 객체의 배열 scores는 변경되지 않았고, deepCopy의 배열만 변경되었다.

Person 클래스의 복사 생성자에서 배열 복사를 scores.clone()을 통해 새로운 배열을 생성하고,

해당 배열을 deepCopy가 참조했기 때문이다.

 

 

💡 방어적 복사

방어적 복사는 원본 객체를 보호하기 위해 내부 필드를 복사한 값을 반환하거나,

외부에서 전달받은 값을 복사하여 사용하는 방식이다.

 

따라서 외부에서 객체를 변경할 수 없으므로 불변성이 보장되고, 값이 변하지 않음을 보장하므로 코드에 대한 신뢰성이 높아진다.

하지만 매번 복사해야 하므로 메모리와 성능 비용 발생한다.

 

 

언제 사용할까?

  • 외부에서 전달된 데이터가 내부에서 변경되지 않도록 방지해야 할 때
  • 불변 객체를 생성하거나 외부로 안전하게 반환할 때
public class Team {
    private final List<Person> persons;

    public Team(List<Person> persons) {
        this.persons = new ArrayList<>(persons); // 방어적 복사
    }

    public List<Person> getPersons() {
        return new ArrayList<>(persons); // 방어적 복사로 반환
    }
}

원본 List<Person>을 복사할 때, 원본 컬렉션 List<Person>와의 참조를 끊고,

new ArrayList<>(persons) 으로 새로운 객체를 생성하여 복사하면 방어적 복사로 본다.

 

 

💡 Collection 객체의 복사 방법

       
데이터 구조 얕은 복사 깊은 복사 방어적 복사
List List.copyOf() 스트림으로 개별 요소 복사 반환 시 new ArrayList<>(list) 사용
Map new HashMap<>(originalMap) 내부 key-value 쌍의 깊은 복사 필요 반환 시 Collections.unmodifiableMap(map) 사용
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
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 29 30 31
글 보관함