5.1 배열의 선언과 생성 (Introduction to Arrays)
프로그래밍 중 성적, 이름, 날짜 등을 저장하기 위해 수많은 변수가 필요한 상황이 있습니다. 이런 수많은 변수들을 효율적으로 다루기 위해 사용하는 것이 배열(Array) 입니다.
1. 배열이 필요한 이유
100명의 학생 점수를 저장한다고 가정해 봅시다.
// 배열 없이 변수만 사용하면 이렇게 됩니다...
int score1 = 85;
int score2 = 90;
int score3 = 78;
// ... score100까지 선언?? 불가능에 가깝습니다.
배열을 사용하면 같은 타입의 변수를 하나의 묶음으로 관리할 수 있습니다.
// 배열로 100명의 점수를 한 줄로!
int[] scores = new int[100];
// 반복문과 함께 일괄 처리
for (int i = 0; i < scores.length; i++) {
scores[i] = 80 + i % 20; // 예시 점수 대입
}
배열의 핵심 특징:
- 같은 타입 의 여러 변수를 하나의 묶음으로 저장
- 각 데이터를 요소(element) 라고 부름
- 요소는 메모리상에 연속적으로 저장됨
- 인덱스(index) 로 각 요소에 접근 (0부터 시작)
2. 배열과 메모리 구조
자바에서 배열은 기본형 데이터와 달리 객체(Object) 취급을 받습니다.
[스택(Stack) 영역] [힙(Heap) 영역]
score ─────────────→ [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ]
(참조변수) index: 0 1 2 3 4
- 스택: 참조변수
score가 저장됩니다. 힙의 배열 주소(참조값)를 담고 있습니다. - 힙: 실제 배열 데이터가 연속된 메모리 공간에 저장됩니다.
int[] score = new int[5];
// score 변수 자체는 스택에 있는 참조변수
// new int[5]로 만들어진 실제 배열은 힙에 존재
System.out.println(score); // [I@7ef88735 (배열의 메모리 주소 출력)
3. 배열의 선언
// 방법 1: 타입 뒤에 [] (권장)
int[] score;
String[] name;
double[] prices;
boolean[] flags;
// 방법 2: 변수명 뒤에 [] (C언어 스타일, 비권장)
int score[];
String name[];
선언만으로는 사용 불가
int[] score;는 참조변수 공간만 만든 것입니다. 아직 실제 배열이 없으므로 score[0]처럼 사용하면 NullPointerException이 발생합니다.
4. 배열의 생성
new 키워드와 크기를 지정하여 실제 배열을 힙 메모리에 생성합니다.
int[] score; // 선언
score = new int[5]; // 크기가 5인 int형 배열 생성
// 대부분 선언과 생성을 동시에 진행합니다.
int[] score = new int[5];
5. 배열의 기본값 (Default Values)
배열 생성 시 각 타입에 맞는 기본값(default value) 으로 자동 초기화됩니다.
| 타입 | 기본값 |
|---|---|
byte, short, int, long | 0 |
float, double | 0.0 |
char | '\u0000' (null 문자) |
boolean | false |
참조형 (String, 객체 등) | null |
int[] intArr = new int[3];
double[] dblArr = new double[3];
boolean[] boolArr = new boolean[3];
String[] strArr = new String[3];
System.out.println(intArr[0]); // 0
System.out.println(dblArr[0]); // 0.0
System.out.println(boolArr[0]); // false
System.out.println(strArr[0]); // null
6. 배열의 초기화
개발자가 원하는 값으로 배열을 직접 초기화할 수 있습니다.
// 방법 1: new 키워드 + 값 나열
int[] score = new int[]{ 50, 60, 70, 80, 90 };
// 방법 2: 축약형 (선언과 동시에만 가능)
int[] score = { 50, 60, 70, 80, 90 };
// 방법 3: 인덱스로 하나씩 대입
int[] score = new int[5];
score[0] = 50;
score[1] = 60;
// ...
// 주의: 변수 선언 후 나중에 초기화할 때는 new 생략 불가
int[] score;
score = new int[]{ 50, 60, 70, 80, 90 }; // OK
// score = { 50, 60, 70 }; // 컴파일 오류!
7. 배열의 길이 (length)
int[] score = { 50, 60, 70, 80, 90 };
System.out.println("배열 길이: " + score.length); // 5
// 인덱스는 0부터 (length-1)까지
for (int i = 0; i < score.length; i++) {
System.out.println("score[" + i + "] = " + score[i]);
}
배열 길이는 변경 불가
자바에서 배열의 길이는 생성 후 절대 변경할 수 없습니다. 더 큰 배열이 필요하면 새 배열을 만들고 복사해야 합니다. 크기가 동적으로 변해야 하면 ArrayList를 사용하세요.
8. ArrayIndexOutOfBoundsException
배열의 유효한 인덱스 범위는 0 ~ length - 1입니다. 이 범위를 벗어나면 런타임 오류가 발생합니다.
int[] arr = new int[3]; // 인덱스: 0, 1, 2
arr[0] = 10; // OK
arr[1] = 20; // OK
arr[2] = 30; // OK
// arr[3] = 40; // ArrayIndexOutOfBoundsException!
// arr[-1] = 5; // ArrayIndexOutOfBoundsException!
// 안전하게 접근하려면 항상 범위 검사
int index = 5;
if (index >= 0 && index < arr.length) {
System.out.println(arr[index]);
} else {
System.out.println("유효하지 않은 인덱스: " + index);
}
9. 배열 순회 방법 4가지
import java.util.Arrays;
int[] numbers = {30, 10, 50, 20, 40};
// 방법 1: 전통 for 문 (인덱스 접근 가능)
System.out.println("방법 1 - for 문:");
for (int i = 0; i < numbers.length; i++) {
System.out.print(numbers[i] + " ");
}
// 방법 2: 향상된 for 문 (간결, 인덱스 불필요할 때)
System.out.println("\n방법 2 - 향상된 for:");
for (int num : numbers) {
System.out.print(num + " ");
}
// 방법 3: Arrays.toString() (빠른 확인용)
System.out.println("\n방법 3 - Arrays.toString():");
System.out.println(Arrays.toString(numbers)); // [30, 10, 50, 20, 40]
// 방법 4: Stream (Java 8+, 함수형 스타일)
System.out.println("방법 4 - Stream:");
Arrays.stream(numbers).forEach(n -> System.out.print(n + " "));
10. 배열 복사
배열의 길이는 변경 불가이므로, 더 큰 배열이 필요할 때 복사합니다.
주의: 참조 복사 vs 값 복사
int[] original = {1, 2, 3, 4, 5};
// 참조 복사 (같은 배열을 가리킴) - 주의!
int[] ref = original;
ref[0] = 99;
System.out.println(original[0]); // 99 - original도 변경됨!
// 값 복사 (진짜 복사)
int[] copy = original.clone();
copy[0] = 100;
System.out.println(original[0]); // 99 - original은 변경 안 됨
4가지 배열 복사 방법
import java.util.Arrays;
int[] src = {1, 2, 3, 4, 5};
// 방법 1: for 루프
int[] copy1 = new int[src.length];
for (int i = 0; i < src.length; i++) {
copy1[i] = src[i];
}
// 방법 2: System.arraycopy() - 가장 빠름
int[] copy2 = new int[src.length];
System.arraycopy(src, 0, copy2, 0, src.length);
// arraycopy(원본, 원본시작, 대상, 대상시작, 복사길이)
// 방법 3: Arrays.copyOf() - 길이 조정 가능
int[] copy3 = Arrays.copyOf(src, src.length); // 동일 길이
int[] copy4 = Arrays.copyOf(src, 8); // 더 큰 배열 (나머지는 0)
// 방법 4: clone() - 간단한 1차원 배열 복사
int[] copy5 = src.clone();
System.out.println(Arrays.toString(copy3)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(copy4)); // [1, 2, 3, 4, 5, 0, 0, 0]
11. 가변 인수(varargs)와 배열
메서드 파라미터에 ...을 사용하면 가변 개수의 인수 를 배열처럼 받을 수 있습니다.
public class VarargsExample {
// int... numbers는 내부적으로 int[] numbers와 동일
static int sum(int... numbers) {
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}
static void printAll(String... messages) {
for (String msg : messages) {
System.out.println(msg);
}
}
public static void main(String[] args) {
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(1, 2, 3, 4, 5)); // 15
System.out.println(sum()); // 0 (인수 없음도 가능)
printAll("안녕", "Hello", "こんにちは");
}
}
12. 메서드 인자/반환값으로 배열 사용
public class ArrayMethodExample {
// 배열을 인자로 받는 메서드
static int getMax(int[] arr) {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 배열을 반환하는 메서드
static int[] createRange(int start, int end) {
int[] result = new int[end - start + 1];
for (int i = 0; i < result.length; i++) {
result[i] = start + i;
}
return result;
}
public static void main(String[] args) {
int[] scores = {85, 92, 78, 96, 88};
System.out.println("최고점: " + getMax(scores)); // 96
int[] range = createRange(1, 5);
System.out.println(Arrays.toString(range)); // [1, 2, 3, 4, 5]
}
}
13. 실전 예제: 학생 점수 통계 계산기
배열을 활용한 완전한 예제입니다.
import java.util.Arrays;
public class ScoreStatistics {
public static void main(String[] args) {
int[] scores = {85, 92, 78, 96, 88, 73, 61, 99, 84, 90};
int n = scores.length;
// 최고점, 최저점, 합계 계산
int max = scores[0];
int min = scores[0];
int sum = 0;
for (int score : scores) {
if (score > max) max = score;
if (score < min) min = score;
sum += score;
}
double average = (double) sum / n;
// 정렬 (원본을 보존하려면 복사 후 정렬)
int[] sorted = scores.clone();
Arrays.sort(sorted);
// 중앙값 계산
double median;
if (n % 2 == 0) {
median = (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0;
} else {
median = sorted[n / 2];
}
// 결과 출력
System.out.println("=== 학생 점수 통계 ===");
System.out.println("점수 목록: " + Arrays.toString(scores));
System.out.println("정렬된 점수: " + Arrays.toString(sorted));
System.out.printf("최고점: %d%n", max);
System.out.printf("최저점: %d%n", min);
System.out.printf("총점: %d%n", sum);
System.out.printf("평균: %.1f%n", average);
System.out.printf("중앙값: %.1f%n", median);
// 학점별 분포
System.out.println("\n학점 분포:");
int[] gradeCounts = new int[5]; // A, B, C, D, F
for (int score : scores) {
if (score >= 90) gradeCounts[0]++;
else if (score >= 80) gradeCounts[1]++;
else if (score >= 70) gradeCounts[2]++;
else if (score >= 60) gradeCounts[3]++;
else gradeCounts[4]++;
}
String[] grades = {"A (90+)", "B (80~89)", "C (70~79)", "D (60~69)", "F (~59)"};
for (int i = 0; i < grades.length; i++) {
System.out.printf("%-12s: %d명%n", grades[i], gradeCounts[i]);
}
}
}
출력:
=== 학생 점수 통계 ===
점수 목록: [85, 92, 78, 96, 88, 73, 61, 99, 84, 90]
정렬된 점수: [61, 73, 78, 84, 85, 88, 90, 92, 96, 99]
최고점: 99
최저점: 61
총점: 846
평균: 84.6
중앙값: 86.5
학점 분포:
A (90+) : 4명
B (80~89) : 3명
C (70~79) : 2명
D (60~69) : 1명
F (~59) : 0명
고수 팁
배열 vs ArrayList 선택 기준:
- 크기가 고정 되어 있고 기본형(int, double 등) 데이터를 다룰 때: 배열이 더 빠르고 메모리 효율적
- 크기가 동적으로 변하거나 다양한 컬렉션 API가 필요할 때:
ArrayList<Integer>사용
// 배열: 고정 크기, 메모리 효율적
int[] arr = new int[10];
// ArrayList: 동적 크기, 다양한 메서드 제공
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.remove(0); // 동적 크기 변경 가능