여기서 말하는 복사의 대상은 바로 '객체'다.

객체를 복사하는데 있어 객체의 '무엇'을 복사하느냐가 깊은 복사와 얕은 복사를 나누는 기준이 되는데 그 대상은 각각 다음과 같다.

Deep Copy Shallow Copy
객체 자체를 복제 (clone) 하여 새 객체 생성 객체의 주소값만 복사

그러면 코드 상에서는 각각 어떻게 다른 결과물을 만들어 내는지 한번 보기로 한다.

우선, Car라고 하는 클래스를 하나 정의하고, 다음과 같이 구현해 보자.


public class Car {
    private String name;
    private int price;
    private String color;

    public Car() {

    }

    public Car(String name) {
        this.name = name;
    }

    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public Car(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Car(String name, int price, String color) {
        this.name = name;
        this.price = price;
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

그리고 메인 함수에서 이 클래스의 인스턴스를 하나 생성한다.

Car c1 = new Car("Lamborghini", 1000000000, "Yellow");

Shallow copy (얕은 복사)

얕은 복사를 하려면 새 인스턴스를 생성하고, 그 인스턴스의 값으로 위에 생성한 c1 인스턴스를 할당해 주면 된다.

Car c2 = c1;

여기서 c2 인스턴스의 이름을 Lamborghini에서 Maserati로 바꿔 보고, 그 이후 결과값을 한번 조회해 본다.

c2.setName("Maserati");
System.out.println(c1.getName());
System.out.println(c2.getName());

// 두 인스턴스의 해시코드 값도 동일하게 출력되어 나온다. 즉 가리키는 주소값이 같은 객체라는 뜻.
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());

실행해 보니 c2 인스턴스의 이름이 바뀌긴 했는데, c1 인스턴스의 이름까지 바뀌는 현상이 발생했다. 왜일까?

그것은 바로, 맨 위에서 언급한 것처럼 이 경우에는 c1 객체의 주소값을 복사하여 c2 인스턴스를 만들었기 때문이다. 즉 두 인스턴스는 힙메모리 상에서 같은 메모리 주소를 참조하고 있으며, 따라서 그 메모리 주소가 갖는 값이 변경되면 그 영향이 c1, c2 둘에게 다 간다.

그렇다면 복사한 인스턴스의 값이 변경되어도 기존 인스턴스에는 영향이 없게 하려면 어떻게 해야 할까? 여기서 등장하는 개념이 '깊은 복사'다.

Deep copy (깊은 복사)

깊은 복사는 객체를 복사하되 이를 별개의 메모리 공간에 할당하는 것이다. 자바에서는 이를 clone (복제)한다고 표현하는데, 이를 위해서는 깊은 복사의 대상이 clone이 가능하도록 Cloneable 인터페이스를 구현하고 있어야 한다. 따라서 위에서 선언한 Car 클래스를 다음과 같이 수정해 본다.

public class Car implements Cloneable {
    ...

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

새로 추가된 메소드는, Cloneable 인터페이스가 갖고 있는 clone() 메소드를 오버라이딩 한 것으로, Car 타입의 인스턴스를 복제하여 리턴한다는 내용이다. 이 메소드를 호출하려면 다음과 같이 하면 된다.

// deep copy
try {
    Car c3 = c1.clone();
    c3.setName("BMW");

    // c1과 c3의 이름이 이번에는 각각 다르게 출력된다. 즉 한쪽의 변화가 다른 한쪽에 영향을 주지 않는다.
    System.out.println(c1.getName());
    System.out.println(c3.getName());

    // 두 해시코드의 값이 다르게 나온다. 즉 두 인스턴스는 각기 다른 주소값을 갖는다.
    System.out.println(c1.hashCode());
    System.out.println(c3.hashCode());
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

clone() 메소드가 CloneNotSupportedException을 던지기 때문에, 이 메소드 실행은 try-catch 구문 내에서 한다.

실행해 보면 한쪽 인스턴스의 값 변화가 다른 한쪽 인스턴스의 값에 영향을 주지 않고 있으며, 각 인스턴스가 가리키고 있는 주소값이 다른 것을 알 수 있다.

반응형

+ Recent posts