Java

[Java] 얕은 복사와 깊은 복사

oneH 2024. 12. 27. 20:38

 

 

프로그래밍에서 자료구조는 매우 중요하다. 그중 배열은 자료구조의 기초이자 핵심이라고 볼 수 있다.

 

배열에 대해 더 상세하고 깊게 배우기 위해 얕은 복사와 깊은 복사에 대해 정리해 보겠다.

 


 

 

우선 코드를 보자. 

 

import java.util.Arrays;

public class arrayCopy {
    public static void main(String[] args) {
        String[] food = {"치킨", "피자"};
        String[] shallowCopy = food;

        System.out.println("원본 배열: " + Arrays.toString(food));
        System.out.println("복사 배열: " + Arrays.toString(shallowCopy));

        shallowCopy[1] = "햄버거";
        System.out.println("==복사 배열 2번째 요소 변경==");

        System.out.println("원본 배열: " + Arrays.toString(food));
        System.out.println("복사 배열: " + Arrays.toString(shallowCopy));

    }
}

 

실행 결과

 

 

해당 코드는 food가 원본 배열 shallowArray가 복사 배열이다.

shallowArray는 food를 대입받았다.

String[] shallowCopy = food;

 

해당 복사는 food와 shallowCopy가 서로 다른 배열인 것 같다. 즉, 배열 안에 요소들만 같고 두 배열은 달라 보인다.

 

하지만 shallowCopy 배열의 [1] 1번 index 요소를 변경하면 food 배열의 요소도 변경된 것을 확인할 수 있다.

 

 

복사본 배열의 값을 변경했을 뿐인데 왜 원본 배열의 값도 변경된 걸까?

이유는 얕은 복사(shallow copy)때문이다.

 

 


 

 

얕은 복사

  얕은 복사를 알아가기 전 우리는 Java에서 데이터 타입이 기본형과 참조형으로 나뉜다는 것을 알 수 있다.

  참조형은 객체를 생성할 때 객체 주소를 참조하는 값 같은 기본형(int, float, double 등)을 제외한 나머지를 참조형이라 한다.

 

  Java에서는 배열도 참조형이다.

 

 

배열이 왜 참조형인가?

1. 객체로 구현되었다.

 

  Java에서 배열이 객체로 구현되었다. Object 클래스에서 상속받은 메서드를 사용할 수 있음.

 

 

2. new 연산자로 생성.

 

  클래스를 만들고 객체를 만들 때 우리는 new 키워드를 사용해 객체를 생성했다. 배열도 마찬가지이다. 일반적으로 배열을 만들 때 우리는 int[] arr = new int[10] 이런식으로 만든다. 

  int[] arr;  => 이대로 끝나면 arr의 값은 null이다.

 

 

배열을 생성할 때 생성 과정에 대해서도 살펴볼 필요가 있다.

 

1. 참조 변수 생성

int[] arr;

참조변수를 생성했지만, 메모리에 할당은 안 함.

2. new int[5];

JVM이 배열 객체를 힙 메모리에 생성. 모든 요소 초기화.

3. 참조 연결

arr은 힙 메모리에 생성된 배열 객체의 주소값을 저장하며 이를 통해 배열 요소에 접근 가능.

 

 

  

 

 

  다시 맨 위 예제 코드에서 복사본 배열의 값을 변경했는데 왜 원본 배열의 값들도 변경된 걸까?

  해당 문제는 배열의 참조변수로 알 수 있다. 

  food와 shallowCopy는 같은 참조값이다. 즉, 메모리에 생성된 한 개의 배열을 참조하고 있다.

  그러므로 food 배열과 shallowCopy 배열은 복사된 것이 아닌 그냥 같은 배열을 공유하고 있다고 볼 수 있다.

 

System.out.println(food);
System.out.println(shallowCopy);

 

  해당 결과는 같은 주소값을 출력한다.

 

 

  즉 값이 둘 다 바뀐 이유는 얕은 복사 때문이다.

 

 


 

 

얕은 복사의 정의

  얕은 복사는 객체의 주소값만 복사한다.

  원본 객체와 복사된 객체가 같은 참조를 공유한다.(참조값이 같음)

  참조형 변수들은 동일한 메모리 주소를 가리키게 된다.

 

얕은 복사의 값 변경

 

  원본 배열과 복사된 배열 모두 동일한 데이터를 공유하므로 복사본 변경 시 원본에도 영향을 준다.

 

 


 

 

깊은 복사

 

 

  얕은 복사를 피하기 위해서는 새 배열을 생성하고 원본 배열의 모든 요소값들을 새 배열에 저장해주면 된다.

 

import java.util.Arrays;

public class DeepCopy {
    public static void main(String[] args) {
        int[] original = {5,8,10};
        int[] deepCopy = new int[original.length];  //원본 배열의 길이만큼 배열 생성.


        for(int i=0; i<deepCopy.length; i++){
            deepCopy[i] = original[i];
        }

        System.out.println("==값 확인==");
        System.out.println(Arrays.toString(original));
        System.out.println(Arrays.toString(deepCopy));

        System.out.println("==복사 배열 값 변경==");
        deepCopy[0] = 50000;
        System.out.println(Arrays.toString(original));
        System.out.println(Arrays.toString(deepCopy));


    }
}

 

 

출력 결과

 

 

  해당 코드는 원본 배열 크기만큼 복사할 배열을 생성하고, 생성한 복사 배열에 원본 배열의 요소들을 각 각 대입 연산으로 넣어주면 된다.

  즉, 기본형 데이터 타입으로 복사한 것이다.

  기본형 데이터 타입은 얕은 복사든 깊은 복사든 독립적이다.

 

 

 

 

  위 코드처럼 반복문으로 깊은 복사를 구현할 수 있지만, Java에서는 깊은 복사와 관련된 다양한 메서드들을 제공한다.

 

1. clone()

2. System.arraycopy(Object src, int srcPos, Object dest, int destPos, int count)

3. Arrays.copyOf()

4. Arrays.copyRange()

 

 

 

package copy;

import java.util.Arrays;

public class DeepCopy {
    public static void main(String[] args) {
        System.out.println("------clone() 사용------");
        int[] original = {5, 8, 10};
        int[] deepCopy = original.clone();
        deepCopy[2] = -80000;
        System.out.println("원본: " + Arrays.toString(original));
        System.out.println("복사: " + Arrays.toString(deepCopy));

        System.out.println("\n------System.arrayCopy() 사용------");
        int[] deepCopy2 = new int[original.length - 1];
        System.arraycopy(original, 0, deepCopy2, 0, 2); //원본 배열의 요소 2개만 복사
        deepCopy2[1] = 99999990;
        System.out.println("원본: " + Arrays.toString(original));
        System.out.println("복사: " + Arrays.toString(deepCopy2));

        System.out.println("\n------Arrays.copyOf() 사용------");
        int[] deepCopy3 = Arrays.copyOf(original, 3);
        deepCopy3[0] = 900;
        System.out.println("원본: " + Arrays.toString(original));
        System.out.println("복사: " + Arrays.toString(deepCopy3));


        System.out.println("\n------Arrays.copyRange() 사용------");
        int[] deepCopy4 = Arrays.copyOfRange(original, 0, 3);
        deepCopy4[2] = -100000;
        System.out.println("원본: " + Arrays.toString(original));
        System.out.println("복사: " + Arrays.toString(deepCopy4));


    }
}

 

 

 


 

 

결론

  

특징 얕은 복사 깊은 복사
복사 대상 참조(주소값)만 복사 객체의 데이터 자체 복사
독립성 원본과 복사본이 연결되어 있음 원본과 복사본이 독립적임 완전히 다름
속도 빠름 상대적으로 느림
메모리 사용 적음 최소 한 개 더 만드니 더 사용
원본에 영향 복사본 수정 시 원본 영향 복사본 수정 시 원본 영향 없음