Java에서의 배열 복사는 얕은복사와 깊은 복사가 있다.
얕은 복사 : 1차원 배열에서의 얕은 복사는 단순 배열 변수의 선언으로 이루어진다.
int[] arrA = {1, 2, 3};
int[] arrB = arrA;
arrB[2] = 5;
System.out.println(arrA[2]); // 5
System.out.println(arrA); // [I@c818063
System.out.println(arrB); // [I@c818063
분명 arrB[2] 를 변경했는데 arrA[2]도 변경된 값으로 출력되는 것을 확인할 수 있다.
또한 arrA와 arrB를 각각 출력해보면 같은 주소값을 참조하고 있음을 알 수 있다.
깊은 복사 : clone() 메서드를 사용해 깊은복사를 할 수 있다.
int[] arrA = {1, 2, 3};
int[] arrB = arrA.clone();
arrB[2] = 5;
System.out.println(arrA[2]); // 3
System.out.println(arrA); // [I@c818063
System.out.println(arrB); // [I@3f0ee7cb
이번에는 arrB[2]를 변경했음에도 arrA[2]에는 영향이 없는 것을 확인할 수 있다.
또한 두 배열을 각각 출력해보면 서로 다른 주소값을 참조하고 있음을 알 수 있다.
2차원 이상의 배열에서는 clone()을 사용해도 깊은 복사가 아닌 얕은 복사가 되는것에 주의하자!
얕은 복사 : 단순 배열 변수의 선언 또는 clone() 메서드를 이용한다.
int[][] arrA = {{1, 2, 3}, {5, 6, 7}};
int[][] arrB = arrA;
int[][] arrC = arrA.clone();
arrB[0][2] = 5;
arrC[1][2] = 10;
System.out.println(arrA[0][2]); // 5
System.out.println(arrA[1][2]); // 10
System.out.println(arrA); // [[I@c818063
System.out.println(arrB); // [[I@c818063
System.out.println(arrC); // [[I@3f0ee7cb
위의 코드처럼 변수의 선언으로 복사한 arrB와 clone()을 통해 복사한 arrC 모두 원본인 arrA의 값에 영향을 주는것을 확인할 수 있다.
그런데 한가지 이상한 점이 있다.
얕은 복사를 했을 arrC 의 주소값이 arrA나 arrB와는 다르게 나온다는 것이다. 이는 1차원 배열에서의 clone() 복사를 잘 생각해보면 알 수 있는데, 1차원 배열의 깊은 복사에서는 배열 안의 값을 그대로 복사해 새로운 주소값을 가진 배열을 만드는 것이다.
이를 그대로 2차원 배열의 clone() 복사에 적용해보면 배열 안의 값 (1차원 배열)을 그대로 복사해 새로운 주소값을 가진 배열을 만들기 때문에 주소값이 다르게 나온 것이다. 직관적으로 배열의 내용물은 그대로 가져오고 껍데기만 새로 만든다고 보면 될 것 같다.
그 증거로 아래 코드를 보면 arrA 와 arrC 내부의 1차원 배열들의 주소값은 모두 같음을 확인할 수 있다.
int[][] arrA = {{1, 2, 3}, {5, 6, 7}};
int[][] arrB = arrA;
int[][] arrC = arrA.clone();
arrB[0][2] = 5;
arrC[1][2] = 10;
System.out.println(arrA[0]); // [I@c818063
System.out.println(arrC[0]); // [I@c818063
System.out.println(arrA[1]); // [I@3f0ee7cb
System.out.println(arrC[1]); // [I@3f0ee7cb
따라서 내부의 1차원 배열의 주소값이 같기 때문에 clone()을 사용해도 원본 배열의 값에 영향을 주는 것이다.
깊은 복사
그렇다면 2차원 배열의 깊은 복사는 어떻게 할까? 두 가지 방법이 있다. (행의 길이가 다른 이중배열은 고려하지 않는다.)
(물론 아래의 두가지 방법은 모두 1차원 배열의 깊은복사에서도 사용 가능하다.)
2중 for문을 통한 복사
int[][] arrA = {{1, 2, 3}, {5, 6, 7}};
int row = arrA.length;
int column = arrA[0].length;
int[][] arrB = new int[row][column];
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
arrB[i][j] = arrA[i][j];
}
}
arrB[0][2] = 5;
System.out.println(arrA[0][2]); // 3
arrB의 값 변경이 원본인 arrA에게 영향이 없음을 확인할 수 있다.
System.arraycopy() 를 통한 복사
System.arraycopy(원본 배열, 원본 배열 시작위치, 새 배열, 새 배열 시작위치, 길이) 와 반복문을 이용해 복사하는 것이다.
int[][] arrA = {{1, 2, 3}, {5, 6, 7}};
int row = arrA.length;
int column = arrA[0].length;
int[][] arrB = new int[row][column];
for (int i = 0; i < row; i++) {
System.arraycopy(arrA[i], 0, arrB[i], 0, arrB[i].length);
}
arrB[0][2] = 5;
System.out.println(arrA[0][2]); // 3
이 역시 위와 마찬가지로 arrB의 값 변경이 원본인 arrA에게 영향이 없음을 확인할 수 있다.
마찬가지로 Arrays.copyOf() 도 System.arraycopy() 를 래핑한 메서드이기 때문에 성능은 같다.
둘의 성능 면에서 차이를 보면 System.arraycopy()나 Arrays.copyOf()이 반복문을 사용해 복사한것 보다 두배가량 빠르다고 한다.
자세한 성능의 차이에 대한 테스트는 Stack overflow 사이트 에서 확인할 수 있다.
가독성 면에서도 copy 라는 이름이 명시된 메서드를 사용하는것이 훨씬 좋으니 반복문을 통한 복사 대신 메서드사용을 지향하자!