[Java] 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)

yujamint·2022년 7월 23일
0

Java

목록 보기
5/6
class Agent { // 요원 클래스
    String name; // 이름
    String nationality; // 국적

    public Agent(String name, String nationality) {
        this.name = name;
        this.nationality = nationality;
    }
}

위와 같이 요원의 이름과 국적을 멤버변수로 갖는 Agent 클래스가 있다.
요원 객체를 하나 생성하고, 그 객체를 복사해서 이름,국적만 바꾸려고 한다. 코드는 다음과 같다.

Agent jett = new Agent("Jett", "Korea");
Agent chamber = jett;

chamber.name = "Chamber";
chamber.nationality = "France";

이제 요원 객체 2개가 잘 생성되었는지 확인하기 위해 출력을 해보면 결과는 다음과 같다.

System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Chamber France
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France

의도와 다르게, jett 객체의 정보도 같이 변경된 것을 확인할 수 있다.
이유는 바로, 얕은 복사를 했기 때문이다.
그렇다면, 얕은 복사는 무엇이고 의도한 결과를 도출하기 위해서는 어떻게 해야될까?

얕은 복사


얕은 복사(Shallow Copy) : '주소값'을 복사한다.

얕은 복사는 주소값을 복사하기 때문에 실제로 참조하고 있는 값은 같다.
위에서 본 상황이 바로 얕은 복사의 예시이다.
즉, jett 객체와 chamber 객체는 같은 값을 참조하고 있었고, 그 값을 변경했기 때문에 jett의 이름과 국적도 함께 변경된 것처럼 보인 것이다.

우리가 원하는대로 복사한 객체에서 수정한 내용이 원래 객체에도 적용되는 것을 막기 위해서는 깊은 복사를 해야 된다.

깊은 복사


깊은 복사(Deep Copy) : '실제값'을 새로운 메모리 공간에 복사한다.

깊은 복사를 구현하는 방법은 다음과 같다.

1. 복사 생성자, 복사 팩터리

class Agent { // 요원 클래스
    String name; // 이름
    String nationality; // 국적

    public Agent(){
    }

    public Agent(String name, String nationality) {
        this.name = name;
        this.nationality = nationality;
    }

    //복사 생성자
    public Agent(Agent origianl) {
        this.name = origianl.name;
        this.nationality = origianl.nationality;
    }

    //복사 팩터리
    public static Agent copy(Agent original) {
        Agent copy = new Agent();
        copy.name = original.name;
        copy.nationality = original.nationality;
        return copy;
    }
}

복사 생성자와 복사 팩터리는 위 코드와 같이 구현한다.
아래 코드는 복사 생성자와 복사 팩터리로 복사한 객체에 대한 수정이 원래 객체에 영향을 미치는지 확인하는 코드이다.

Agent jett = new Agent("Jett", "Korea");
Agent chamber = new Agent(jett); // 복사 생성자로 복사
Agent phoenix = Agent.copy(jett); // 복사 팩터리로 복사

//복사 생성자로 복사한 객체 수정
chamber.name = "Chamber";
chamber.nationality = "France";

//복사 팩터리로 복사한 객체 수정
phoenix.name = "Phoenix";
phoenix.nationality = "UK";

System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France
System.out.println(phoenix.name + " " + phoenix.nationality); // 출력결과 : Phoenix UK

원래 객체에 영향을 미치지 않고 잘 수정된 것을 확인할 수 있다.

2. 객체를 직접 생성하여 복사

Agent jett = new Agent("Jett", "Korea");
Agent chamber = new Agent(); // 객체 생성

// jett 객체에 접근하여 직접 복사
chamber.name = jett.name;
chamber.nationality = jett.nationality;

System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Jett Korea

chamber.name = "Phoenix";
chamber.nationality = "UK";

System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France

이 방법은 객체에 접근해서 멤버변수 값을 직접 읽어온 뒤에 새로 만든 객체의 멤버변수에 할당하는 것이다.

3. Cloneable 인터페이스 구현

class Agent implements Cloneable{ // Cloneable 인터페이스 구현
    String name;
    String nationality;

    @Override
    protected Agent clone() throws CloneNotSupportedException { // Object 클래스의 clone() 메소드 구현
        return (Agent) super.clone();
    }

    public Agent(String name, String nationality) {
        this.name = name;
        this.nationality = nationality;
    }
}

Agent 클래스에서 Cloenable 인터페이스를 상속받는다.
Cloneable 인터페이스를 구현하기 위해서는 Object 클래스의 clone() 메소드를 반드시 구현해야 된다.

public static void main(String[] args) throws CloneNotSupportedException{
    Agent jett = new Agent("Jett", "Korea");
    Agent chamber = jett.clone(); // clone() 메소드로 복사

    // 복사 결과 확인
    System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
    System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Jett Korea

    // 복사한 객체 수정
    chamber.name = "Phoenix";
    chamber.nationality = "UK";

    // 수정 결과 확인
    System.out.println(jett.name + " " + jett.nationality); // 출력결과 : Jett Korea
    System.out.println(chamber.name + " " + chamber.nationality); // 출력결과 : Chamber France
}

clone() 메소드를 통해서 깊은 복사가 잘 된 것을 확인할 수 있다.

하지만, 이 방법은 final 인스턴스 또는 배열이 아닌 경우 추천하지 않는 방법이라고 한다.

1차원 배열의 경우에는 clone() 메소드를 통해서 쉽게 복사할 수 있다.

int[] arr = {1, 2, 3, 4};
int[] copy = arr.clone();

for (int x : copy) System.out.print(x + " "); // 출력결과 : 1 2 3 4

배열을 복사하는 다른 방법

  • System.arraycopy
int[] arr = {1, 2, 3, 4};
int[] copy = new int[];
System.arraycopy(arr, 0, copy, 0, arr.length);

for (int x : copy) System.out.print(x + " "); // 출력결과 : 1 2 3 4

원래 배열, 원래 배열 시작 위치, 새 배열, 새 배열 시작 위치, 배열 길이 순서로 파라미터를 입력한다.

2차원 배열 복사

2차원 배열의 경우에는 clone() 메소드나 System.arraycopy를 통해 바로 복사할 수 없다.

  • 2중 for문 순회
int[][] arr = {{1, 2},
               {3, 4}};
int[][] copy = new int[2][2];

for (int i=0; i<2; i++){
    for (int j=0; j<2; j++) {
        copy[i][j] = arr[i][j];
    }
}

2중 for문을 순회하며 값을 하나씩 입력한다.

  • 반복문 + System.arraycopy
int[][] arr = {{1, 2},
               {3, 4}};
int[][] copy = new int[2][2];

for (int i=0; i<2; i++){
    System.arraycopy(arr,0,copy,0,arr.length);
}

System.arraycopy를 2차원 배열의 행 수만큼 실행한다.

References


https://zzang9ha.tistory.com/372 clone() 재정의에 대한 내용 언급

https://jackjeong.tistory.com/100

https://velog.io/@bokiri409/%EB%AC%B8%EB%B2%95%EC%9E%90%EB%B0%94JAVA-%EB%B0%B0%EC%97%B4%EC%9D%98-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%ACDeep-Copy-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%ACShallow-Copy

profile
개발 기록

0개의 댓글