[java] 배열의 얕은 복사, 깊은 복사

Java의 복사와 copyOf(), copyOfRange(), arraycopy()에 대해 공부한다.

Posted by Seoyoung Lee on January 21, 2021 · 10 mins read

Java의 Copy

자바의 복사 방법에는 얕은 복사(Shallow copy)와 깊은 복사(Deep copy)가 있다. 단순한 primitive type의 변수일 경우 얕은 복사로도 문제 없이 진행되지만, reference type(객체, 배열 등)의 변수일 경우 깊은 복사를 사용해야지만 객체의 실제 데이터를 복사할 수 있다.

배열의 복사에 대해 정리하면, 얕은 복사의 경우 원본 배열이나 복사된 배열이 변경될 때 상대 배열의 값이 같이 변경되지만 깊은 복사의 경우 원본 배열이나 복사된 배열이 변결될 때 서로간의 값은 바뀌지 않는다.


얕은 복사 (Shallow copy)

아래는 1차원 배열에 대해 얕은 복사를 하는 코드이다.

int[] a = {1, 2, 3, 4};
int[] b = a;
System.out.println(Arrays.toString(a)); // [1, 2, 3, 4]

System.out.println(Arrays.toString(b)); // [1, 2, 3, 4]


// 원본 배열 값 변경

a[1] = 10;
System.out.println(Arrays.toString(a)); // [1, 10, 3, 4]

System.out.println(Arrays.toString(b)); // [1, 10, 3, 4]


// 복사한 배열 값 변경

b[3] = 1111;
System.out.println(Arrays.toString(a)); // [1, 10, 3, 1111]

System.out.println(Arrays.toString(b)); // [1, 10, 3, 1111]

추가적인 설명을 하면, 얕은 복사에서는 =연산자를 사용해 해당 변수에 담겨있는 값을 복사한다. 배열의 경우 해당 변수에 heap의 데이터를 가리키는 참조값이 저장되어있기 때문에 참조값이 복사되어 같은 원본 배열을 가리키게 되는 것이다.


깊은 복사 (Deep copy)

아래는 1차원 배열에 대해 깊은 복사를 하는 코드이다.

int[] a = {1, 2, 3, 4};
int[] b = new int[a.length]; 
for (int i = 0; i < a.length; i++) {
    b[i] = a[i];
}
System.out.println(Arrays.toString(a)); // [1, 2, 3, 4]

System.out.println(Arrays.toString(b)); // [1, 2, 3, 4]


// 원본 배열 값 변경

a[1] = 10;
System.out.println(Arrays.toString(a)); // [1, 10, 3, 4]

System.out.println(Arrays.toString(b)); // [1, 2, 3, 4]


// 복사한 배열 값 변경

b[3] = 1111;
System.out.println(Arrays.toString(a)); // [1, 10, 3, 4]

System.out.println(Arrays.toString(b)); // [1, 2, 3, 1111]

이렇게 for문을 이용해 복사를 하는 방법도 있지만 java에서 지원하는 다양한 메소드(method)들을 활용하면 더 편하게 깊은 복사를 할 수 있다.


다양한 복사 메서드 (method)


Object.clone()

int[] a = {1, 2, 3, 4};
int[] b = a.clone();

자바에서 모든 클래스의 부모 클래스는 Object 클래스이고 Object 클래스에는 clone() 메서드가 있다. 객체 생성 후, 복제를 할 때 사용된다. clone() 메소드를 이용해 새로운 배열을 생성하고 b변수에는 a변수와는 다른 참조값을 가지게된다.

getter를 통해 배열을 가져와서 그 배열을 변경해버리면 원본 배열까지 변경되는 문제가 있다. 이러한 문제를 해결하기 위해 배열에 대한 getter 함수에서는 clone() 메서드를 사용한다.

int[] scores = {89, 70, 94, 100};

public int[] getScores() {
	return scores.clone();
}

Arrays.copyOf() / Arrays.copyOfRange()

int[] a = { 1, 2, 3, 4, 5, 6, 7 };

// a 배열 전체 복사

int[] b1 = Arrays.copyOf(a, a.length);
int[] b2 = Arrays.copyOfRange(a, 0, a.length);

// a 배열 3개 요소값 복사 (첫 번째 요소부터 시작해서 3개 가져옴)

int[] b11 = Arrays.copyOf(a, 3);

// a 배열 3개 요소값 복사 (4번째 요소부터 시작해서 3개 가져옴)

int[] b22 = Arrays.copyOfRange(a, 3, 6);

Arrays 클래스의 copyOf(), copyOfRange() 메소드를 사용하면 배열의 전체 또는 부분을 복사할 수 있다.

차이점이라면 copyOf()는 무조건 처음부터 지정 길이를 복사하지만, copyOfRange()는 시작점의 위치또한 지정할 수 있다.


System.arraycopy()

int[] a = {1, 2, 3, 4, 5, 6, 7};

// a 배열 전체 복사

int[] b1 = new int[7];
System.arraycopy(a, 0, b1, 0, 7);

// a 배열 3개 요소값 복사 (4번째 요소부터 시작해서 3개 가져옴)

int[] b2 = new int[3];
System.arraycopy(a, 4, b2, 0, 3);

System 클래스의 arraycopy() 메소드를 사용하여 배열 전체 또는 일부분의 데이터를 복사할 수 있다. 인자를 5개 받으며 그에 대한 설명은 아래와 같다.

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);


2차원 배열의 복사

1차원 배열의 경우 배열 각 요소는 기본형 변수이기 때문에 위 메서드들을 이용해 깊은 복사가 가능하다. 하지만 2차원 배열의 경우에는 변수가 가리키는 1차원 배열의 요소에 참조형 변수가 들어있어 배열 크기만큼 위 메서드를 또 이용해야 깊은 복사가 가능하다.

이 또한 마찬가지로 2중 for문을 사용하여 2차원 배열의 깊은 복사를 할 수 있지만 1중 for문과 메서드들을 이용하면 조금 더 편하게 2차원 배열의 깊은 복사를 할 수 있다.

2중 for문

int a[][] = { {1,2,3},
              {4,5,6},
              {7,8,9} };
int b[][] = new int[a.length][a[0].length];

for (int i = 0; i < a.length; i++) {
	for (int j = 0; j < a[i].length; j++){
    b[i][j] = a[i][j];
  }
}

Object.clone()

int a[][] = { {1,2,3},
              {4,5,6},
              {7,8,9} };
int b[][] = new int[a.length][a[0].length];

for (int i = 0; i < a.length; i++) {
	b[i] = a[i].clone();
}

System.arraycopy()

int a[][] = { {1,2,3},
              {4,5,6},
              {7,8,9} };
int b[][] = new int[a.length][a[0].length];

for(int i=0; i < b.length; i++){
    System.arraycopy(a[i], 0, b[i], 0, a[0].length);
}