본문 바로가기
Language/JAVA

[JAVA] 깊은 복사 vs 얕은 복사

by YoonJong 2023. 8. 20.
728x90

깊은 복사 vs 얕은 복사

#Java

 

참고 : [Java] - 깊은 복사(Deep Copy) vs 얕은 복사(Shallow Copy)

 

깊은 복사 : ‘실제 값’을 새로운 메모리 공간에 복사하는 것

얕은 복사 : ‘주소 값’을 복사 → 참조하고 있는 실제값은 같다.

  • Collection 은 clone() 이라는 메서드를 이용해서 얕은 복사를 만들 수 있다.

 

깊은 복사를 구현하는 방법은 여러가지

  1. Cloneable 인터페이스 구현
  2. 복사 생성자
  3. 복사 팩터리

 

  • Cloneable 을 사용하지 말자.
    Cloneable 인터페이스는 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스지만, 아쉽게도 의도한 목적을 제대로 이루지 못했다. 여기서 큰 문제점은 clone 메서드가 선언된 곳이 Cloneable이 아닌 OBject이고, 그 마저도 protected이다. 그래서 Cloneable을 구현하는 것만으로는 외부 객체에서 clone 메소드를 호출할 수 없다. 리플렉션을 사용하면 가능하지만, 100% 성공하는 것도 아니다. 
    이러한 여러 문제점을 가진 인터페이스이지만, Cloneable 방식은 널리 쓰이고 있어서 잘 알아두는 것이 좋다. 
    Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스라면 Cloneable을 구현해도 위험이 크지는 않지만, 성능 최적화 관점에서 검토한 후 별다른 문제가 없을 때만 드물게 허용해야 한다. 
    기본 원칙은 '복제 기능은 생성자와 팩터리를 이용하는게 최고' 라는 것이다.
    단, 배열만은 clone 메소드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.

 

클래스 

package study.querydsl.copyTest;

public class Member {
    private String name;
    private int age;

    public Member() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

 

테스트

package study.querydsl.copyTest;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;

 class CopyObjectTest {

    @Test
    @DisplayName("얕은 복사는 주소값과 실제값 모두 같다.")
    void 얕은복사() {
        Member original = new Member("jyj", 29);
        Member copy = original; // 얕은 복사

        System.out.println("original.hashCode() = " + original.hashCode());
        System.out.println("copy.hashCode() = " + copy.hashCode());

        //2032891036
        //2032891036

        copy.setAge(20);

        System.out.println("original = " + original.getAge());
        System.out.println("copy = " + copy.getAge());

        // 20
        // 20

    }

// 복사하는 과정을 따로 메서드로 만드는 것이 좋다.
    @Test
    @DisplayName("깊은 복사는 주소값이 다르고 실제값만 복사한다.")
    void 깊은복사() {
        Member original = new Member("jyj", 29);
        Member copy = new Member(original.getName(), original.getAge());

        System.out.println("original.hashCode() = " + original.hashCode());
        System.out.println("copy.hashCode() = " + copy.hashCode());

        //633240419
        //116734858

        copy.setAge(20);

        System.out.println("original = " + original.getAge());
        System.out.println("copy = " + copy.getAge());

        //29
        //20

    }

    @Test
    @DisplayName("Collection 은 clone() 메서드를 통해 얕은복사 생성")
    void List_복사() {
        Member a = new Member("jyj", 29);
        Member b = new Member("jyj", 30);

        ArrayList<Member> list = new ArrayList<Member>();
        list.add(a);
        list.add(b);

        Object cloneList = list.clone();

        System.out.println("list.hashCode() = " + list.hashCode());
        System.out.println("cloneList.hashCode() = " + cloneList.hashCode());

        //949273941
        //949273941
    }

}

 

 

 

728x90

댓글