본문 바로가기

Hub Development/Java

[Java] 얕은 복사는 어디에 사용 될까? (feat. 얕은복사와 깊은복사의 차이)

728x90

📌 개요


🔹 우리가 보통 복사를 원하는 경우에 얕은 복사가 아닌 깊은 복사가 되는 것을 원한다. 그렇다면, 얕은 복사는 어디에 쓰이는 걸까? 라는 의문에 알아보게 되었다. 먼저 얕은 복사와 깊은 복사에 대해 알아보자.

 

🧪 얕은 복사 (Shallow Copy)  란?


얕은 복사는 복사하려는 배열의 '주소값'을 복사한다.

 

🔹 즉, 얕은 복사가 된 경우, 원본배열 또는 복사된 배열의 값이 변경된다면 서로에게 영향을 준다.

🖋  1차원 배열의 얕은 복사

public class ArrayShallowCopyExample {
    public static void main(String[] args) {
        int[] A = {1, 2, 3};
        int[] B = A; // 얕은 복사
        
        System.out.println(A[0]); // 1 출력
        System.out.println(B[2]); // 3 출력

        B[0] = 5; // B의 첫 번째 요소 변경
        A[2] = 10; // A의 세 번째 요소 변경

        System.out.println(A[0]); // 5 출력
        System.out.println(B[2]); // 10 출력
    }
}

🖋  2차원 배열의 얕은 복사

public class ArrayShallowCopy2DExample {
    public static void main(String[] args) {
        int[][] A = {{1, 2, 3}, {4, 5, 6}};
        int[][] B = A.clone(); // 얕은 복사
        
        System.out.println("A[0][0]: " + A[0][0]); // A의 첫 번째 요소 출력 1
        System.out.println("B[0][0]: " + B[0][0]); // B의 첫 번째 요소 출력 1

        B[0][0] = 10; // B의 첫 번째 요소 변경

        System.out.println("A[0][0]: " + A[0][0]); // 10
        System.out.println("B[0][0]: " + B[0][0]); // 10
    }
}

 

🧪 깊은 복사 (Deep Copy) 란?


객체의 실제 값을 새로운 객체로 복사한다.

 

🔹즉, 복사된 배열이나 원본 배열이 변경될 때 서로에게 영향을 주지 않는다.

🖋  1차원 배열의 깊은 복사 ( Object.clone() )

public class ArrayDeepCopyExample {
    public static void main(String[] args) {
        int[] A = {1, 2, 3};
        int[] B = A.clone(); // 깊은 복사
        
        System.out.println(A[0]); // 1 출력
        System.out.println(B[2]); // 3 출력

        B[0] = 5; // B의 첫 번째 요소 변경
        A[2] = 10; // A의 세 번째 요소 변경

        System.out.println(A[0]); // 1 출력
        System.out.println(B[2]); // 3 출력
    }
}

🖋  1차원 배열의 깊은 복사 ( System.arraycopy() )

🔹 `System.arraycopy(복사하려는 배열, 복사하려는 배열의 시작 위치, 새 배열, 새로운 배열의 시작 위치, 길이)` 이다. 이때, `Arrays.copyOf()`는 System.arraycopy() 메서드를 래핑한 함수이고 2배가량 빠르다고 한다.

public class ArrayDeepCopyExample {
    public static void main(String[] args) {
        int[] A = {1, 2, 3};
        int[] B = new int[A.length];

        System.arraycopy(A, 0, B, 0, A.length); // System.arraycopy()를 사용한 복사

        System.out.println(A[0]); // 1 출력
        System.out.println(B[2]); // 3 출력

        B[0] = 5; // B의 첫 번째 요소 변경
        A[2] = 10; // A의 세 번째 요소 변경

        System.out.println(A[0]); // 1 출력
        System.out.println(B[2]); // 3 출력
    }
}

🖋  2차원 배열의 깊은 복사 ( 생성자를 이용하는 방법 )

public class ArrayDeepCopy2DExample {
    public static void main(String[] args) {

        ArrayList<Integer> list = new ArrayList<>();  // initialCapacity : 10
        list.add(new Integer(0));

        ArrayList<Integer> list2 = new ArrayList<>(list);  // Deep copy
        list2.add(new Integer(1));
        list2.add(new Integer(2));

        // 원본 객체 출력
        System.out.println(list.toString()); // [0]
        // 사본 객체 출력
        System.out.println(list2.toString()); // [0,1,2]
	}
}

🖋  2차원 배열의 깊은 복사 ( 다양한 방법 )

public class ArrayDeepCopy2DExample {
    public static void main(String[] args) {
        int[][] A = {{1, 2, 3}, {4, 5, 6}};
        int[][] B = new int[A.length][A[0].length];

        // 배열 A의 각 행을 복사하여 배열 B에 대입
        for (int i = 0; i < A.length; i++) {
            B[i] = A[i].clone();
        }
        
        // 배열 A의 각 행을 System.arraycopy()를 사용하여 배열 B에 복사
//        for (int i = 0; i < A.length; i++) {
//            System.arraycopy(A[i], 0, B[i], 0, A[i].length);
//       }

        // 배열 A의 각 요소 값을 일일이 복사하여 배열 B에 대입
//        for (int i = 0; i < A.length; i++) {
//            for (int j = 0; j < A[i].length; j++) {
//                B[i][j] = A[i][j];
//           }
//        }

        System.out.println("A[0][0]: " + A[0][0]); // 1
        System.out.println("B[0][0]: " + B[0][0]); // 1

        B[0][0] = 10; // B의 첫 번째 요소 변경

        System.out.println("A[0][0]: " + A[0][0]); // 1 출력
        System.out.println("B[0][0]: " + B[0][0]); // 10 출력
    }
}

 

👻  결론


🔹위의 내용을 통해 얕은 복사와 깊은 복사의 차이에 대해 알 수 있었다. 하지만, 아직까지 얕은 복사가 사용되는 예시가 나오지 않았는데 얕은 복사를 사용하는 예시는 아래와 같다. 

public class ShallowCopyPrimitiveTypesExample {
    public static void main(String[] args) {
        int originalInt = 5;
        double originalDouble = 3.14;
        boolean originalBoolean = true;

        // 얕은 복사
        int copiedInt = originalInt;
        double copiedDouble = originalDouble;
        boolean copiedBoolean = originalBoolean;

        // 복사된 변수들 변경
        copiedInt = 10;
        copiedDouble = 2.71;
        copiedBoolean = false;

        // 원본 변수들 출력
        System.out.println("Original int: " + originalInt); // 5 출력
        System.out.println("Original double: " + originalDouble); // 3.14 출력
        System.out.println("Original boolean: " + originalBoolean); // true 출력

        // 복사된 변수들 출력
        System.out.println("Copied int: " + copiedInt); // 10 출력
        System.out.println("Copied double: " + copiedDouble); // 2.71 출력
        System.out.println("Copied boolean: " + copiedBoolean); // false 출력
    }
}

 

🔹이 방식은 분명 얕은 복사지만 서로에게 영향을 끼치고 있지 않는다. 그 이유는 기본 타입 변수이기 때문이다. 기본 타입 변수는 스택에 주소값이 아닌 실제 값을 데이터로 갖고 있기 때문에 얕은 복사를 하더라도 주소값이 복사되는 것이 아닌 실제 값이 복사된다.

 

반면에 참조 타입 변수는 객체에 대한 참조(메모리 주소)를 가지고 있다. 따라서 참조 타입 변수를 복사할 때는 단순히 메모리 주소만을 복사하게 되어 두 변수가 같은 객체를 참조하게 된다. 이 경우 두 변수 중 하나를 통해 객체를 수정하면 다른 변수를 통해서도 그 변경 내용이 반영되므로 주의해야 한다. 

 

이처럼 얕은 복사는 기본 타입변수의 값을 복사하는데에 사용되고 있다.