[Kotlin] 얕은 복사(Shallow Copy)와 깊은 복사(Depp Copy)

케니스·2023년 2월 8일
1

Kotlin

목록 보기
5/5

안녕하세요. 이번 글에서는 얕은복사(Shallow Copy)와 깊은복사(Deep Copy)에 대해서 알아보려고합니다.


자바나 코틀린으로 개발을 하다보면 객체를 복사할 일이 생기는데 이 때 주의해서 복사하지 않으면 의도치 않은 결과를 마주할 수 있습니다. 객체를 복사하는 방법은 크게 얕은복사(Shallow Copy)와 깊은복사(Deep Copy) 두 가지 방식이 있습니다.


얕은 복사(Shallow Copy)

얇은 복사는 원본 객체에 대한 객체를 만들고 원본 객체의 주소값을 참조하는것을 의미합니다. 객체는 인스턴스화 되면서 메모리에 주소를 할당하게 되는데요 이 때 할당된 주소의 값을 참조하게 되는 것이 얕은 복사라고 합니다. 코드로 한번 어떤 의미인지 살펴보겠습니다.

public class User {
  int id;
  String name;
  
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
  
  public void changeName(String name) {
    this.name = name;
  }
  
  public void changeId(int id) {
    this.id = id;
  }
}


public void main() {
  User kenneth = new User(1, "kenneth");  
  User daniel = kenneth;
  kenneth.changeName("daniel");
  kenneth.changeId(2);
  
  System.out.println(kenneth); // User(name='daniel', id=2)
  System.out.println(daniel); // User(name='daniel', id=2)
}

위에 코드에서 는 유저 이름이 각 kennethdaniel 이 노출되는것을 기대하지만 모두 daniel이라는 값을 가지고 있는 것을 볼 수 있습니다. 이렇게 된 이유는 daniel은 실제 값을 복사한게 아닌 참조값을 복사했습니다.

  User kenneth = new User(1, "kenneth");  
  User daniel = kenneth;

User인스턴스를 생성하고 daniel 인스턴스는 kenneth의 참조값을 저장합니다. kenneth와 daniel의 주소값이 동일하므로 49cfaae주소값을 동일하게 바라보고있습니다.


  kenneth.changeName("daniel");
  kenneth.changeId(2);

이제 kenneth에 접근하여 이름을 daniel로 변경해보겠습니다.

kenneth와 daniel 인스턴스가 참조하는 힙 데이터 내의 실제값이 수정되었습니다 결국 kenneth와 daniel 모두 변경된 값을 동일하게 참조합니다. 하지만 우리는 실제 객체의 값이 복사되는 것을 원합니다. 이런 경우에는 깊은 복사(Deep Copy)를 해야합니다.


깊은복사(Deep Copy)

깊은 복사(Deep Copy)는 실제 값을 새로운 메모리 공간에 복사하는 것입니다.

깊은 복사를 하는 방법은 크게 3가지가 있습니다.

  • 복사 생성자 또는 복사 팩터리를 이용
  • 직접 객체를 생성하여 복사
  • Cloneable을 이용한 clone() 재정의


복사 생성자, 복사 팩터리를 이용

public class User {
  int id;
  String name;
  
  // 복사 생성자
  public User(User user) {
    this.id = user.id;
    this.name = user.name;
  }
  // 복사 팩터리
  public static User newInstance(User user) {
    User copy = new User();
    copy.name = user.name;
    copy.id = user.id;
    return copy
  }
}

직접 객체를 생성

User kenneth = new User(1, "kenneth");  
User daniel = new User();

daniel.setName(kenneth.name);
daniel.setId(kenneth.Id);

kenneth.changeName("kenneth 2")
kenneth.changeId(3)

Cloneable을 이용한 clone() 재정의

public class User implements Cloneable {
    String name;
    int it;

    @NonNull
    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

Cloneable을 이용한 복사는 이펙티브 자바에서 권장하지 않는데 그 이유는 final 클래스는 위험이 크지 않지만 그 외에는 성능 최적화 관점에서 문제가 없을 때만 허용해야 하기 때문에 복제는 생성자와 팩터리를 이용하는게 베스트라고 한다.


코틀린에서 data class의 copy()는 깊은 복사일까?

코틀린 data class의 copy() 함수는 마치 깊은 복사를 해주는 것처럼 헷갈릴 수 있지만 copy()는 얇은 복사입니다. 이유는 깊은 복사는 객체의 변수들을 전부 깊은 복사를 해주지만 copy() 함수는 data class내의 Primitive 타입을 제외한 커스텀 객체는 깊은 복사를 지원하지 않습니다.


또한 코틀린에서 Immutable한 객체는 처음 할당되고 프로그램이 종료될 때 까지 값이 변경되지 않으므로 깊은 복사와 얇은 복사의 선택권이 없고 경계하지 않아도됩니다.

참고

profile
노력하는 개발자입니다.

1개의 댓글

comment-user-thumbnail
2023년 6월 28일

좋은 글 감사합니다! 덕분에 잘못알고 있던걸 깨달았습니다
(제목에 depp 오타난것 같습니다)

답글 달기