본문 바로가기

Java

깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)

얕은 복사(Shallow Copy) 란

 

다음과 같은 Java 코드를 보자

int[] arr1 = {1,2,3};
int[] arr2 = arr1;
		
arr2[0] = 10;
System.out.println(arr1[0]);  // output : 10
System.out.println(arr2[0]);  // output : 10

arr1[1] = 9;
System.out.println(arr1[1]);  // output : 9
System.out.println(arr2[1]);  // output : 9

 

위 코드에서는 분명 arr2의 0번째 데이터를 10으로 변경했는데 arr2만 아니라 arr1의 0번째 데이터도 10으로 변경된 것을 볼 수 있다. 또한 arr1의 1번째 데이터를 9로 변경했는데 arr2의 1번째 데이터도 9로 변경된 것을 볼 수 있다.


이처럼 복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조(메모리 내의 같은 값을 가리킴)를 공유하는 복사를 얕은 복사라고 한다.


위 코드에서는 int[] arr2 = arr1; 가 진행될 때 arr1의 참조가 arr2로 복사된다. 따라서 둘 다 동일한 참조를 공유하고 있기 때문에 한쪽 객체가 변경되면 다른 객체도 같이 변경되는 것이다.

 

  • 참조(Reference) : 객체를 가리키는 변수로 객체의 메모리 주소를 간접적으로 다룬다.

 

깊은 복사(Deep Copy) 란

 

다음과 같은 Java 코드를 보자

int[] arr1 = {1,2,3};
int[] arr2 = arr1.clone();

arr2[0] = 10;
System.out.println(arr1[0]);  // output : 1
System.out.println(arr2[0]);  // output : 10

 

얕은 복사 케이스와 달리 이번에는 arr2의 값을 변경했다고 해서 arr1의 값이 변경되지 않는다.

 

이처럼 복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조(메모리 내의 같은 값을 가리킴)를 공유하지 않는 복사를 깊은 복사라고 한다.


 

깊은 복사 방법

 

 

1. 복사 생성자 

복사 생성자는 생성자의 매개변수로 자기 자신과 같은 타입의 객체를 받는다. 이렇게 작성된 깊은 복사 메소드는 생성자 내에서 새로운 객체의 속성에 원본 객체의 속성을 할당한다.

class Apple {
	int size;
	public Apple(Apple a){
		this.size = a.size;
	}
}

class Tree {
	int height;
	Apple apple;
	public Tree(Tree t){
		this.height = t.height;
		this.apple = new Apple(t.apple);
	}
}

 

 

2. Cloneable 인터페이스와 clone() 메소드 사용

Java에서는 Cloneable 인터페이스와 clone() 메소드를 사용하여 객체를 복사할 수 있다. 하지만 기본 clone() 메소드는 얕은 복사를 수행하므로 깊은 복사를 위해서는 이를 재정의해야 한다.
(물론 참조형 객체가 아닌 int[]와 같은 기본형 객체에서는 clone() 메소드를 사용하여 복사해도 깊은 복사가 진행된다. )

class Address implements Cloneable {
    String city, country;
    
    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return new Address(city, country);
    }
}

class Person implements Cloneable {
    String name;
    Address address;
    
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone();
        return cloned;
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person original = new Person("John", new Address("Seoul", "Korea"));
        Person copy = (Person) original.clone();
        
        copy.address.city = "Busan";
        System.out.println(original.address.city); // Seoul
    }
}

 

 

3. 직렬화(Serialization) 사용

직렬화를 사용하면 객체를 바이트 스트림으로 변환한 후 다시 객체로 복원할 때 새로운 메모리 공간에 객체가 생성된다. 이 과정에서 모든 객체가 새로운 인스턴스로 만들어지므로 원본 객체와 동일한 객체를 가리키는 참조가 존재하지 않게 된다. 이로 인해 객체의 깊은 복사가 이루어진다.

import java.io.*;

class Address implements Serializable {
    String city, country;
    
    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}

class Person implements Serializable {
    String name;
    Address address;
    
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Person deepCopy() {
        try {
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(byteOut);
            out.writeObject(this);

            ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
            ObjectInputStream in = new ObjectInputStream(byteIn);
            return (Person) in.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Person original = new Person("John", new Address("Seoul", "Korea"));
        Person copy = original.deepCopy();
        
        copy.address.city = "Busan";
        System.out.println(original.address.city); // Seoul
    }
}

출처 :
https://velog.io/@dm911/Java-%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-%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
https://developer.mozilla.org/ko/docs/Glossary/Shallow_copy
https://howtodoinjava.com/java/serialization/how-to-do-deep-cloning-using-in-memory-serialization-in-java/

 

'Java' 카테고리의 다른 글

상속(Inheritance)  (0) 2024.07.12
동일성(identity)과 동등성(equality)  (1) 2024.06.07
String / StringBuilder / StringBuffer  (0) 2024.05.23
BigDecimal  (0) 2024.05.20
String과 String Constant Pool  (0) 2024.03.26