Java Array
배열(Array)이란?
배열은 같은 타입의 데이터를 연속된 메모리 공간에 저장하는 자료구조입니다. 쉽게 말해 같은 종류의 데이터를 한 줄로 나란히 저장하는 상자라고 생각하면 됩니다.
배열의 핵심 특징:
첫째, 크기가 고정되어 있습니다. 한 번 생성하면 크기를 변경할 수 없습니다. 만약 5개짜리 배열을 만들었다면, 나중에 10개로 늘릴 수 없습니다. 새로운 배열을 만들어 데이터를 복사해야 합니다.
둘째, 인덱스로 접근합니다. 배열의 각 요소는 0부터 시작하는 번호(인덱스)로 접근할 수 있습니다. 첫 번째 요소는 인덱스 0, 두 번째 요소는 인덱스 1입니다.
셋째, 접근 속도가 매우 빠릅니다. 인덱스를 알면 O(1) 시간, 즉 즉시 해당 요소에 접근할 수 있습니다. 이것이 배열의 가장 큰 장점입니다.
넷째, 같은 타입만 저장할 수 있습니다. 정수 배열에는 정수만, 문자열 배열에는 문자열만 저장할 수 있습니다.
배열은 언제 사용할까?
배열은 데이터의 개수가 정해져 있고, 인덱스를 통한 빠른 접근이 필요할 때 사용합니다. 예를 들어 한 학급의 학생 30명의 점수를 저장한다면 배열이 적합합니다. 학생 수는 고정되어 있고, "5번째 학생의 점수"처럼 특정 위치의 데이터에 빠르게 접근해야 하기 때문입니다.
반대로 데이터가 계속 추가되거나 삭제되는 경우에는 ArrayList 같은 가변 크기 자료구조가 더 적합합니다. 배열은 크기가 고정되어 있어 요소를 추가하려면 새 배열을 만들고 기존 데이터를 복사해야 하는 번거로움이 있습니다.
java.util.Arrays 클래스 소개
java.util.Arrays는 배열을 다루는 다양한 유틸리티 메서드를 제공하는 클래스입니다. 이 클래스의 모든 메서드는 static으로 선언되어 있어, 객체를 생성하지 않고 Arrays.메서드명() 형태로 바로 사용할 수 있습니다.
이 클래스는 정렬, 검색, 비교, 복사, 채우기 등 배열 작업에 필요한 거의 모든 기능을 제공합니다. Java 개발자라면 반드시 숙지해야 할 핵심 유틸리티입니다.
사용하기 위해서는 파일 상단에 import java.util.Arrays;를 추가해야 합니다.
1. 배열 정렬 - Arrays.sort()
기본 사용법
Arrays.sort() 메서드는 배열을 오름차순으로 정렬합니다. 숫자는 작은 것부터 큰 순서로, 문자열은 사전순으로 정렬됩니다.
import java.util.Arrays;
int[] numbers = {5, 2, 8, 1, 9};
Arrays.sort(numbers);
// 결과: [1, 2, 5, 8, 9]
왜 중요한가?: 정렬은 데이터 처리의 기본입니다. 정렬된 데이터는 검색이 빠르고, 중복 제거나 통계 계산이 쉽습니다. 예를 들어 학생들의 점수를 정렬하면 최고점, 최저점, 중간값을 쉽게 찾을 수 있습니다.
시간 복잡도: O(n log n)입니다. 1만 개 데이터를 정렬하는데 약 13만 번의 비교 연산이 필요합니다. 이는 매우 효율적인 편입니다.
내부 알고리즘: 기본 타입(int, double 등)에는 Dual-Pivot Quicksort를, 객체 타입에는 Timsort를 사용합니다. 둘 다 매우 빠른 정렬 알고리즘입니다.
부분 정렬
배열의 특정 범위만 정렬하고 싶을 때 사용합니다.
import java.util.Arrays;
int[] arr = {5, 2, 8, 1, 9, 3, 7};
Arrays.sort(arr, 2, 5); // 인덱스 2~4만 정렬
// 결과: [5, 2, 1, 8, 9, 3, 7]
실무 활용: 대용량 데이터에서 상위 N개만 정렬하고 싶을 때 유용합니다. 전체를 정렬하는 것보다 훨씬 빠릅니다.
내림차순 정렬
기본 타입(int, double 등)은 내림차순을 직접 지원하지 않습니다. 래퍼 클래스(Integer, Double 등)를 사용해야 합니다.
Integer[] nums = {5, 2, 8, 1, 9};
Arrays.sort(nums, Collections.reverseOrder());
// 결과: [9, 8, 5, 2, 1]
주의사항: int[]를 Integer[]로 변환해야 합니다. 이는 약간의 메모리 오버헤드가 있으므로, 성능이 매우 중요한 경우에는 오름차순 정렬 후 배열을 뒤집는 방법을 고려하세요.
커스텀 정렬
객체 배열을 특정 기준으로 정렬할 때 Comparator를 사용합니다.
User[] users = {
new User("John", 30),
new User("Alice", 25),
new User("Bob", 35)
};
// 나이순 정렬
Arrays.sort(users, (u1, u2) -> u1.getAge() - u2.getAge());
// 이름순 정렬
Arrays.sort(users, Comparator.comparing(User::getName));
실무 활용: 상품을 가격순으로 정렬, 게시글을 날짜순으로 정렬 등 실무에서 가장 자주 사용하는 패턴입니다.
2. 배열 검색 - Arrays.binarySearch()
기본 사용법
Arrays.binarySearch() 메서드는 정렬된 배열에서 특정 값을 찾습니다. 이진 검색 알고리즘을 사용하여 매우 빠릅니다.
int[] numbers = {1, 3, 5, 7, 9, 11, 13};
int index = Arrays.binarySearch(numbers, 7);
// 결과: 3 (7은 인덱스 3에 위치)
핵심 주의사항: 반드시 정렬된 배열에서만 사용해야 합니다! 정렬되지 않은 배열에서는 잘못된 결과가 나옵니다.
시간 복잡도: O(log n)입니다. 1백만 개 데이터에서도 최대 20번의 비교로 찾을 수 있습니다. 선형 검색(O(n))보다 훨씬 빠릅니다.
찾는 값이 없을 때
값을 찾지 못하면 음수를 반환합니다. 반환값은 -(삽입 위치 + 1) 형태입니다.
int[] numbers = {1, 3, 5, 7, 9};
int result = Arrays.binarySearch(numbers, 6);
// 결과: -4 (6이 삽입될 위치는 인덱스 3, 따라서 -(3+1) = -4)
실무 활용: 음수를 양수로 변환하면 삽입 위치를 알 수 있습니다. int insertPos = -(result + 1); 이렇게 하면 데이터를 정렬 상태로 유지하면서 삽입할 위치를 찾을 수 있습니다.
부분 검색
배열의 특정 범위 내에서만 검색할 수 있습니다.
int[] numbers = {1, 3, 5, 7, 9, 11, 13};
int result = Arrays.binarySearch(numbers, 2, 5, 7);
// 인덱스 2~4 범위에서 7을 검색
// 결과: 3
선형 검색과의 비교
정렬되지 않은 배열이나 작은 배열에서는 단순 반복문으로 검색하는 것이 더 나을 수 있습니다.
// 선형 검색 (정렬 불필요)
int[] arr = {5, 2, 8, 1, 9};
int target = 8;
int index = -1;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
index = i;
break;
}
}
언제 무엇을 사용할까?:
- 데이터가 100개 미만이고 정렬되지 않았다면 → 선형 검색
- 데이터가 많고 이미 정렬되어 있다면 → 이진 검색
- 데이터가 많고 정렬되지 않았지만 검색을 자주 한다면 → 한 번 정렬 후 이진 검색
3. 배열 복사 - Arrays.copyOf() & System.arraycopy()
Arrays.copyOf() - 전체 또는 크기 조정 복사
Arrays.copyOf()는 배열을 복사하면서 크기를 조정할 수 있습니다.
int[] original = {1, 2, 3, 4, 5};
int[] copy = Arrays.copyOf(original, original.length);
// 결과: [1, 2, 3, 4, 5]
int[] larger = Arrays.copyOf(original, 8);
// 결과: [1, 2, 3, 4, 5, 0, 0, 0]
int[] smaller = Arrays.copyOf(original, 3);
// 결과: [1, 2, 3]
실무 활용:
- 배열 크기를 늘려야 할 때 (동적 배열 구현)
- 배열의 일부만 필요할 때
- 배열을 안전하게 복사할 때 (원본 보호)
주의사항: 참조 타입 배열은 얕은 복사(shallow copy)가 됩니다. 배열은 복사되지만 배열 안의 객체는 같은 것을 참조합니다.
Arrays.copyOfRange() - 범위 지정 복사
배열의 특정 범위만 복사합니다.
int[] original = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] range = Arrays.copyOfRange(original, 2, 7);
// 인덱스 2~6을 복사 (끝 인덱스 7은 미포함)
// 결과: [3, 4, 5, 6, 7]
실무 활용:
- 대용량 데이터의 일부만 처리할 때
- 페이징 처리 (예: 10개씩 보여주기)
- 데이터 분할 처리
System.arraycopy() - 고성능 복사
가장 빠른 배열 복사 메서드입니다. 네이티브 코드로 구현되어 있어 성능이 매우 뛰어납니다.
int[] source = {1, 2, 3, 4, 5};
int[] dest = new int[5];
System.arraycopy(source, 0, dest, 0, source.length);
// arraycopy(원본, 원본시작위치, 대상, 대상시작위치, 복사길이)
언제 사용하나?:
- 대용량 데이터 복사
- 성능이 중요한 상황
- 배열의 일부를 다른 배열로 복사
- ArrayList 같은 컬렉션의 내부 구현
Arrays.copyOf() vs System.arraycopy():
- Arrays.copyOf(): 사용하기 쉬움, 새 배열 자동 생성
- System.arraycopy(): 더 빠름, 세밀한 제어 가능
4. 배열 비교 - Arrays.equals() & Arrays.deepEquals()
Arrays.equals() - 1차원 배열 비교
두 배열의 내용이 같은지 비교합니다.
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = arr1;
System.out.println(arr1 == arr2); // false (주소 비교)
System.out.println(arr1 == arr3); // true (같은 객체)
System.out.println(Arrays.equals(arr1, arr2)); // true (내용 비교)
핵심 개념: == 연산자는 배열의 주소를 비교하고, Arrays.equals()는 배열의 내용을 비교합니다.
비교 방식:
- 두 배열의 길이가 같은지 확인
- 모든 요소를 순서대로 비교
- 하나라도 다르면 false 반환
Arrays.deepEquals() - 다차원 배열 비교
2차원 이상의 배열을 비교할 때는 deepEquals()를 사용해야 합니다.
int[][] matrix1 = {{1, 2}, {3, 4}};
int[][] matrix2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.equals(matrix1, matrix2)); // false!
System.out.println(Arrays.deepEquals(matrix1, matrix2)); // true
왜 deepEquals가 필요한가?: 2차원 배열은 "배열의 배열"입니다. equals()는 첫 번째 차원만 비교하고, deepEquals()는 모든 차원을 재귀적으로 비교합니다.
실무 활용:
- 행렬 연산 결과 검증
- 게임 보드 상태 비교
- 이미지 데이터 비교 (픽셀 배열)
5. 배열 채우기 - Arrays.fill()
기본 사용법
배열의 모든 요소를 특정 값으로 채웁니다.
int[] arr = new int[5];
Arrays.fill(arr, 10);
// 결과: [10, 10, 10, 10, 10]
실무 활용:
- 배열 초기화 (모든 값을 -1로 초기화 등)
- 기본값 설정
- 알고리즘에서 방문 체크 배열 초기화
부분 채우기
배열의 특정 범위만 채울 수 있습니다.
int[] arr = new int[10];
Arrays.fill(arr, 2, 7, 99); // 인덱스 2~6만 채움
// 결과: [0, 0, 99, 99, 99, 99, 99, 0, 0, 0]
활용 예시:
- 특정 구간만 초기화
- 배열의 일부를 기본값으로 리셋
- 게임에서 특정 영역 초기화
2차원 배열 채우기
2차원 배열은 각 행을 따로 채워야 합니다.
int[][] matrix = new int[3][4];
for (int[] row : matrix) {
Arrays.fill(row, 5);
}
// 모든 요소가 5인 3x4 행렬
주의사항: 2차원 배열을 한 번에 채우는 메서드는 없습니다. 반복문으로 각 행을 채워야 합니다.
6. 배열 변환 - Arrays.toString() & Arrays.deepToString()
Arrays.toString() - 1차원 배열 출력
배열을 읽기 쉬운 문자열로 변환합니다.
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr); // [I@주소값 (의미없음)
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5]
왜 필요한가?: 배열을 직접 출력하면 메모리 주소만 나옵니다. toString()을 사용하면 사람이 읽을 수 있는 형태로 변환됩니다.
디버깅 필수: 개발 중 배열의 내용을 확인할 때 가장 많이 사용하는 메서드입니다.
Arrays.deepToString() - 다차원 배열 출력
2차원 이상의 배열을 출력할 때 사용합니다.
int[][] matrix = {{1, 2}, {3, 4}, {5, 6}};
System.out.println(Arrays.toString(matrix)); // [[I@주소, [I@주소, [I@주소]
System.out.println(Arrays.deepToString(matrix)); // [[1, 2], [3, 4], [5, 6]]
실무 활용:
- 행렬 데이터 로깅
- 게임 보드 상태 출력
- 알고리즘 디버깅
7. List 변환 - Arrays.asList()
배열을 List로 변환
배열을 고정 크기 List로 변환합니다.
String[] arr = {"A", "B", "C"};
List<String> list = Arrays.asList(arr);
// 결과: [A, B, C]
핵심 주의사항: 반환되는 List는 고정 크기입니다. add()나 remove()를 호출하면 UnsupportedOperationException이 발생합니다.
List<String> fixedList = Arrays.asList("A", "B", "C");
// fixedList.add("D"); // 에러!
fixedList.set(0, "Z"); // OK! (요소 수정은 가능)
가변 크기 List로 만들기
진짜 가변 크기 List가 필요하면 ArrayList로 감싸야 합니다.
String[] arr = {"A", "B", "C"};
List<String> list = new ArrayList<>(Arrays.asList(arr));
list.add("D"); // OK!
실무 팁: 초기 데이터가 있는 List를 만들 때 자주 사용합니다.
List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Orange"));
기본 타입 배열의 함정
기본 타입 배열(int[], double[] 등)은 직접 변환할 수 없습니다.
int[] nums = {1, 2, 3};
// List<Integer> list = Arrays.asList(nums); // 컴파일 에러!
// 해결 방법: Stream 사용
List<Integer> list = Arrays.stream(nums)
.boxed()
.collect(Collectors.toList());
8. 배열 요소 개수 확인하기
length 속성
배열의 크기는 length 속성으로 확인합니다. (메서드가 아님!)
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr.length); // 5
String과의 차이:
- 배열: arr.length (속성)
- 문자열: str.length() (메서드)
- List: list.size() (메서드)
실제 데이터 개수 세기
배열에서 특정 조건을 만족하는 요소의 개수를 세는 방법입니다.
int[] numbers = {1, 2, 3, 0, 4, 0, 5};
// 0이 아닌 요소 개수
int count = 0;
for (int num : numbers) {
if (num != 0) {
count++;
}
}
// count = 5
Stream 사용:
long count = Arrays.stream(numbers)
.filter(n -> n != 0)
.count();
실무 활용:
- 유효한 데이터 개수 확인
- 특정 조건 만족하는 항목 수 계산
- 통계 데이터 수집
2차원 배열의 크기
int[][] matrix = new int[3][4];
System.out.println(matrix.length); // 3 (행의 개수)
System.out.println(matrix[0].length); // 4 (열의 개수)
9. 배열에서 요소 찾기 (contains)
배열에는 contains() 메서드가 없습니다. 여러 방법으로 요소를 찾을 수 있습니다.
방법 1: 선형 검색 (정렬 불필요)
int[] arr = {5, 2, 8, 1, 9};
int target = 8;
boolean found = false;
for (int num : arr) {
if (num == target) {
found = true;
break;
}
}
장점: 정렬이 필요 없음 단점: 느림 (O(n)) 사용 시기: 작은 배열, 정렬되지 않은 배열
방법 2: 이진 검색 (정렬 필요)
int[] arr = {1, 2, 5, 8, 9}; // 정렬되어 있어야 함
int target = 8;
boolean found = Arrays.binarySearch(arr, target) >= 0;
장점: 매우 빠름 (O(log n)) 단점: 배열이 정렬되어 있어야 함 사용 시기: 큰 배열, 검색을 자주 하는 경우
방법 3: Stream 사용
int[] arr = {5, 2, 8, 1, 9};
int target = 8;
boolean found = Arrays.stream(arr).anyMatch(n -> n == target);
장점: 코드가 간결함 단점: 약간 느릴 수 있음 사용 시기: 코드 가독성이 중요한 경우
방법 4: List로 변환
String[] arr = {"Apple", "Banana", "Orange"};
List<String> list = Arrays.asList(arr);
boolean found = list.contains("Banana"); // true
주의: 객체 배열에서만 사용 가능합니다.
10. 배열에서 최대값/최소값 찾기
반복문 사용
int[] numbers = {45, 23, 67, 12, 89, 34};
int max = numbers[0];
int min = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) max = numbers[i];
if (numbers[i] < min) min = numbers[i];
}
// max = 89, min = 12
효율적: 배열을 한 번만 순회 (O(n))
Stream 사용
int max = Arrays.stream(numbers).max().orElse(Integer.MIN_VALUE);
int min = Arrays.stream(numbers).min().orElse(Integer.MAX_VALUE);
간결함: 코드가 짧고 읽기 쉬움
정렬 후 찾기
Arrays.sort(numbers);
int min = numbers[0];
int max = numbers[numbers.length - 1];
주의: 정렬하면 원본 배열이 변경됩니다 (O(n log n))
Arrays 클래스 메서드 총정리
| 메서드 | 용도 | 시간 복잡도 | 주의사항 |
| sort() | 배열 정렬 | O(n log n) | 원본 배열 변경됨 |
| binarySearch() | 이진 검색 | O(log n) | 정렬된 배열 필수 |
| copyOf() | 배열 복사 | O(n) | 얕은 복사 |
| copyOfRange() | 범위 복사 | O(n) | 끝 인덱스 미포함 |
| equals() | 배열 비교 (1차원) | O(n) | 내용 비교 |
| deepEquals() | 배열 비교 (다차원) | O(n) | 재귀적 비교 |
| fill() | 배열 채우기 | O(n) | - |
| toString() | 문자열 변환 (1차원) | O(n) | 디버깅용 |
| deepToString() | 문자열 변환 (다차원) | O(n) | 디버깅용 |
| asList() | List 변환 | O(1) | 고정 크기 List |
실무에서 가장 많이 사용하는 패턴
1. 배열 정렬 후 중복 제거
int[] arr = {5, 2, 8, 2, 9, 5, 1};
int[] unique = Arrays.stream(arr).distinct().sorted().toArray();
// 결과: [1, 2, 5, 8, 9]
2. 배열 합계와 평균
int[] scores = {85, 90, 78, 92, 88};
int sum = Arrays.stream(scores).sum();
double avg = Arrays.stream(scores).average().orElse(0);
3. 배열 필터링
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] evens = Arrays.stream(numbers)
.filter(n -> n % 2 == 0)
.toArray();
// 결과: [2, 4, 6, 8, 10]
4. 배열 뒤집기
int[] arr = {1, 2, 3, 4, 5};
int left = 0, right = arr.length - 1;
while (left < right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
// 결과: [5, 4, 3, 2, 1]
5. 두 배열 합치기
int[] arr1 = {1, 2, 3};
int[] arr2 = {4, 5, 6};
int[] merged = IntStream.concat(
Arrays.stream(arr1),
Arrays.stream(arr2)
).toArray();
// 결과: [1, 2, 3, 4, 5, 6]
핵심 정리
Arrays 클래스 핵심 메서드
- 정렬: Arrays.sort() - 가장 자주 사용
- 검색: Arrays.binarySearch() - 정렬 후 사용
- 복사: Arrays.copyOf() - 배열 확장 시
- 비교: Arrays.equals() - 내용 비교
- 채우기: Arrays.fill() - 초기화
- 변환: Arrays.toString() - 디버깅 필수