OOP & JAVA

[JAVA] 깊은복사 (clone , 복사 생성자)

쉽코기 2021. 12. 18. 22:22

1. 깊은 복사 vs 얕은복사

 

얕은 복사는 단순히 객체를 복사하는 것을 의미한다. 객체 자체를 복사했기때문에 모체인 p의 맴버변수가 수정되면 p2의 맴버 변수값 또한 바뀌게 된다. p2 는 그냥 힙에 있는 p의 값을 가리키고 있을 뿐이다.

Person p = new Person();
Person p2 = p;

p.setAge = 10;
println(p2.getAge()) // 10

 

객체가 아닌 원시타입의 변수는 다음 처럼 대입하더라도 그 메모리가 힙에 생성되는 것이 아니기 때문에 자연스럽게 깊은 복사가 이루어진다. 그러나 객체나 배열에 대해서 독립적으로 복사해서 사용하는 깊은 복사를 사용하려면 뒤에 나올 두 방법을 사용하여 구현할 수 있다.

 

2. clone 을 통한 깊은복사 구현

 

clone 메서드를 오버라이딩 하여 사용하면 깊은 복사를 사용할 수 있다. 이때 조심해야할 점이 있다.

 

만약 클래스 안에 또다른 객체나 배열이 존재한다면 이 값은 clone 메서드로 복사 되지 않는다. 

 

이럴 때는 내부 객체의 클래스에서 clone 메서드를 오버라이딩 해준뒤 최종적으로 깊은 복사하고 싶은 객체의 clone메서드 구현체에서 들고 있는 객체를 복사하여 대입해주어야한다.  

public class Phone {
...

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Phone phone = (Phone) super.clone();
        phone.battery = (Battery) this.battery.clone();
        return phone;
    }
}


public class Battery implements Cloneable{
...

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

clone 메서드는 복사 변수에 대해 새로운 메모리를 할당해주고 그 안에 값들을 모체의 값들을 그대로 대입해주는 방식이다.

 

 

2-1 . Clonable 인터페이스

일반적으로 인터페이스에는 메서드가 존재하고 이를 강제하게 설계되어있지만 Clonable 인터페이스는 마크인터페이스로 복제가 가능하다는 표시만해준다. 따라서 Clonable 인터페이스를 implement 하지 않아도 clone 메서드를 오버라이딩 할 수 있다.

 

2-1 . Clone 함수의 단점

  • 캐스팅 : Object 반환 형으로 사용할 clone 하여 사용할때마다 캐스팅이 필요하다.
  • final 맴벼 제어 : 복사 객체가 final 맴버 변수를 가진다면 이를 제어할 수 없습니다.

 

3. 복사 생성자를 통한 깊은 복사

위와 같은 단점을 해결할 수 있는 복사 생성자 입니다. 

 

위에는 객체를 들고 있을때의 복사

아래는 배열을 들고 있을 때의 복사 예시입니다.

 

    Class Stage{
   
   // 일반 생성자
    public Stage(int stageNum , int height, int width) {
        this.stageNum = stageNum;
        this.height = height;
        this.width = width;
    }

// 복사 생성자
    public Stage(final Stage stage) {
        this(stage.stageNum, stage.height, stage.width);
    }
 }
    Class Stage{
   
   // 일반 생성자
    public Stage(int stageNum, int[][] map, int height, int width) {
        this.stageNum = stageNum;
        this.map = map;
        this.height = height;
        this.width = width;

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
              ... // 맵 생성 과정
            }
        }
    }

// 복사 생성자
    public Stage(final Stage stage) {
        this(stage.stageNum, stage.map, stage.height, stage.width);

		// map은 배열 이기 때문에 다음 과 같은 복사 과정이 필요하다.
        int[][] cloneMap = new int[stage.width][stage.height];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                cloneMap[j][i] = stage.map[j][i];
            }
        }
        this.map = cloneMap;

    }
    
    ...
    }