✔️ 한 줄 요약
방법 | 장점 | 상황 | 단점 |
Arrays.copyOfRange | 간결하고 사용하기 쉬움 | 배열의 특정 범위를 복사할 때 | 특정 범위 복사에만 사용 가능 |
Arrays.copyOf | 간단하고 사용하기 쉬움 | 배열의 처음부터 일정 길이만큼 복사할 때 | 배열 길이를 명시적으로 지정해야 함 |
System.arraycopy | 매우 빠르고 효율적 | 성능이 매우 중요한 경우 | 복잡한 사용법 |
ArrayList | 리스트 작업에 유용 | 리스트 작업이 필요할 때 | 성능이 배열에 비해 떨어질 수 있음 |
Stream | 함수형 프로그래밍에 적합 | 함수형 프로그래밍을 사용할 때 | 성능이 배열 복사보다 떨어질 수 있음 |
for문(수동 복사) | 유연성이 높음 | 특별한 로직이 필요할 때 | 코드가 길어지고 복잡해질 수 있음 |
Java에서 배열을 복사하는 방법에는 여러가지가 있다. 각 방법들의 장단점과 사용법, 쓰이는 상황에 대해 알아보자.
1. Arrays.copyOfRange(T[] original, int from, int to)
int[] originalArray = {1, 2, 3, 4, 5};
int[] copiedArray = Arrays.copyOfRange(originalArray, 1, 4);
// copiedArray는 {2, 3, 4}를 포함
사용법: Arrays.copyOfRange(원본 배열, 시작 인덱스, 종료 인덱스)
Arrays.copyOfRange()는 내부적으로 System.arraycopy() 메소드를 사용해 기본적으로 복사 성능이 빠르다. 하지만 내부적으로 범위 검사와 길이 계산으로 인해 약간의 오버헤드가 발생한다. 원본 배열의 시작 인덱스부터 종료 인덱스-1 까지 범위를 복사하여 새로운 배열을 만드는 메소드로 배열의 특정 범위를 복사하거나 자를 때 유용하다. 종료 인덱스가 복사된 배열에 포함되지 않으므로 범위 사용에 주의가 필요하다 !
2. Arrays.copyOf(T[] original, int newLength)
int[] originalArray = {1, 2, 3, 4, 5};
int[] copiedArray = Arrays.copyOf(originalArray, 3);
// copiedArray는 {1, 2, 3}을 포함
사용법: Arrays.copyOf(원본 배열, 복사할 길이)
Arrays.copyOf()도 Arrays.copyOfRange()와 마찬가지로 내부적으로 System.arraycopy() 메소드를 사용해 성능이 빠르다. 내부적으로 길이를 검사하는 로직으로 인해 약간의 오버헤드가 발생한다. 원본 배열을 복사할 길이 만큼 복사한 새로운 배열을 만드는 메소드로 배열의 처음부터 일정길이만큼 복사하거나 자를 때 유용하다.
3. System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
int[] originalArray = {1, 2, 3, 4, 5};
int[] copiedArray = new int[3];
System.arraycopy(originalArray, 1, copiedArray, 0, 3);
// copiedArray는 {2, 3, 4}를 포함
사용법: System.arraycopy(원본 배열, 원본 시작 인덱스, 복사 배열, 복사 시작 인덱스, 복사할 길이)
System.arraycopy()는 네이티브 메소드 호출로 인해 배열 복사 시 성능적으로 가장 뛰어나다. 원본 배열의 시작 인덱스부터 복사할 길이만큼을 복사 배열의 복사 시작 인덱스에 복사하는 메소드로 사용법이 다른 메소드들과 비교해 다소 복잡하게 느껴질 수 있다. 원본 배열과 복사 배열의 타입이 호환되지 않거나 타입 변환이 불가능 한 경우, 복사할 인덱스의 길이가 배열의 크기를 초과하는 경우와 같이 잘못된 사용으로 인해 여러가지 예외가 발생할 수 있어 주의해야 한다.
Arrays.copyOfRange()와 Arrays.copyOf()의 내부 코드를 살펴보면 내부적으로 System.arraycopy()를 사용하고 있다.
public static int[] copyOfRange(int[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
int[] copy = new int[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
때문에 기본적인 복사 성능은 거의 동일하지만, Arrays.copyOfRange()는 범위 검사와 길이 계산에서의 추가 로직이, Arrays.copyOf()는 길이 체크에서의 추가 로직으로 인해 System.arraycopy()보다 약간의 오버헤드가 있을 수 있다.
4. ArrayList 생성
int[] originalArray = {1, 2, 3, 4, 5};
List<Integer> list = new ArrayList<>();
for (int i = 1; i < 4; i++) {
list.add(originalArray[i]);
}
Integer[] copiedArray = list.toArray(new Integer[0]);
// copiedArray는 {2, 3, 4}를 포함
사용법: ArrayList를 생성하여 add()를 통해 요소를 추가한 후 toArray()로 배열로 변환
ArrayList를 사용하는 경우 동적으로 배열의 크기를 조절할 수 있어 배열의 크기가 정해지지 않은 경우 사용하기에 좋고, 리스트 작업이 필요한 경우 유용하다. 다만 오토박싱과 언박싱으로 인한 오버헤드와 배열에 비해 느려 대량의 데이터를 처리할 때 성능 저하가 발생할 수 있으므로 주의하자.
5. Stream 사용 (Java 8 이상)
int[] originalArray = {1, 2, 3, 4, 5};
int[] copiedArray = Arrays.stream(originalArray, 1, 4).toArray();
// copiedArray는 {2, 3, 4}를 포함
사용법: Arrays.stream(원본 배열, 시작 인덱스, 종료 인덱스).toArray()
Arrays.stream()은 Java 8부터 제공되는 Stream을 사용해 원본 배열의 시작 인덱스부터 종료 인덱스-1 까지 범위를 복사하여 새로운 배열을 만드는 메소드다. 스트림을 사용해서 병렬 처리 등 다양한 스트림 기능들을 사용할 수 있다. 스트림에 익숙한 사람이라면 코드를 짧고 간결하게 작성하며 사용할 수 있겠지만, 익숙하지 않으면 사용하고 이해하는데 어려움을 겪을 수 있다. 성능적으로도 배열 복사보다 떨어질 수 있다.
6. for문 사용(수동 복사)
int[] originalArray = {1, 2, 3, 4, 5};
int[] copiedArray = new int[3];
for (int i = 1; i < 4; i++) {
copiedArray[i - 1] = originalArray[i];
}
// copiedArray는 {2, 3, 4}를 포함
사용법: for 루프를 사용하여 원본 배열에서 원하는 범위를 복사
가장 간단하고 원시적인 방법으로 for문을 사용해 복사하면 특정 로직을 추가하기 쉬워 유연성이 높은 코드를 작성할 수 있다. 직접 복사로 인해 성능도 준수한 편이다. 다만 코드가 길어지고 복잡해질 수 있으며 인덱스를 직접 관리해야하니 주의하자.