[Java] 배열

Java 입문 — 배열

김영한의 자바 입문 강의 내용을 정리한 글이다.

배열이 필요한 이유

같은 타입의 값을 여러 개 다뤄야 할 때 변수를 일일이 선언하는 방식은 한계가 있다.

int student1 = 90; int student2 = 80; int student3 = 70; int student4 = 60; int student5 = 50; System.out.println("학생1 점수: " + student1); System.out.println("학생2 점수: " + student2); System.out.println("학생3 점수: " + student3); System.out.println("학생4 점수: " + student4); System.out.println("학생5 점수: " + student5);

학생이 5명이라서 변수도 5개다. 학생이 100명이 되면 int 변수를 100개 선언해야 한다. 출력 코드도 100줄이 된다. 변수 이름이 모두 다르기 때문에 반복문으로 묶을 수도 없다.

이렇게 같은 타입의 변수를 반복해서 선언하고 반복해서 사용하는 문제를 해결하는 것이 바로 배열이다.


배열의 선언과 생성

배열은 같은 타입의 변수를 하나로 묶어서 사용하기 편하게 만든 구조다.

int[] students; // 1. 배열 변수 선언 students = new int[5]; // 2. 배열 생성

두 단계로 나뉜다는 점이 일반 변수와 다르다.

1단계 — 배열 변수 선언

int[] students;는 "int형 배열을 담을 수 있는 변수를 선언한다"는 뜻이다. 일반 변수와의 차이는 타입 뒤에 []가 붙는다는 점이다. 이 시점에서 배열이 만들어진 것이 아니다. 변수만 만들어졌을 뿐이고, 아직 배열은 존재하지 않는다.

비어 있음 int[] students

2단계 — 배열 생성

new int[5]int형 변수 5개를 연속으로 담을 수 있는 배열을 새로 만든다는 뜻이다. new는 새로 생성한다는 의미이고, int[5]는 int형 5개짜리라는 뜻이다. 배열을 생성하면 내부 값이 타입에 따라 자동으로 초기화된다.

타입기본값
int, long, short, byte0
double, float0.0
booleanfalse
String, 객체null
비어 있음 int[] students 0 0 0 0 0 [0] [1] [2] [3] [4] 5개의 int 공간, 0으로 자동 초기화

3단계 — 배열 참조값 보관

배열을 생성하면 JVM은 해당 배열에 접근할 수 있는 참조값(reference), 즉 메모리 주소를 반환한다. 이 참조값이 앞서 선언한 배열 변수 students에 저장된다.

int[] students = new int[5]; // 1. 배열 생성 // 2. new int[5]의 결과로 참조값(예: x001) 반환 // 3. students 변수에 x001 저장

이후 students를 통해 배열에 접근할 때는 변수에 저장된 참조값을 따라가서 실제 배열 데이터에 도달하는 방식으로 동작한다.

System.out.println(students);를 출력하면 [I@4617c264 같은 값이 나온다. [I는 int형 배열을 뜻하고, @ 뒤의 16진수가 참조값(힙의 메모리 주소)이다.

스택(Stack) x001 int[] students 힙(Heap) 0 0 0 0 0 [0] [1] [2] [3] [4] x001

배열과 메모리 구조 — 힙(Heap)

배열이 왜 이렇게 참조값을 사용하는지 이해하려면 JVM의 메모리 구조를 알아야 한다. JVM은 메모리를 크게 스택(Stack)힙(Heap)으로 나눠 사용한다.

영역저장 대상생명주기
스택(Stack)기본형 변수 값, 참조형 변수의 참조값(주소)메서드 호출 시 생성, 종료 시 즉시 제거
힙(Heap)배열, 객체(new로 생성된 것들)GC(가비지 컬렉터)가 수거할 때까지 유지

int a = 10;처럼 기본형 변수는 값 자체가 스택에 저장된다. 반면 new int[5]로 만든 배열은 에 저장된다.

배열이 힙에 저장되는 이유는 크기가 동적으로 결정될 수 있기 때문이다.

int size = scanner.nextInt(); // 사용자가 입력한 값 new int[size]; // 런타임에 크기가 결정됨

기본형은 컴파일 시점에 크기가 확정된다(int는 항상 4바이트). 그러나 배열은 크기가 실행 시점에 결정될 수 있다. 스택은 컴파일 시점에 크기가 고정되어야 하는 구조이기 때문에, 가변 크기 데이터는 힙에 저장한다.

힙에 저장된 배열은 메서드가 끝나도 바로 사라지지 않는다. 어떤 변수도 이 배열을 참조하지 않을 때 가비지 컬렉터(GC)가 수거한다.

스택(Stack) 참조값(주소) x001 int[] students int a = 10 → 값 10 직접 저장 힙(Heap) 0 0 0 0 0 [0] [1] [2] [3] [4] @ x001 · int[5] · 힙에 저장

기본형 vs 참조형

배열을 배우면서 자바의 변수 타입이 두 종류로 나뉜다는 것을 알 수 있다.

기본형(Primitive Type)int, long, double, boolean 등 값 자체를 직접 저장하는 타입이다. 변수에 실제 데이터가 들어있고, 크기가 컴파일 시점에 고정된다.

참조형(Reference Type)int[] students처럼 데이터가 있는 곳의 주소(참조값)를 저장하는 타입이다. 변수에는 실제 데이터가 없고 힙 어딘가를 가리키는 주소만 있다. 배열뿐 아니라 이후에 배울 객체와 클래스도 모두 참조형이다.

구분기본형참조형
저장 내용값 자체참조값(메모리 주소)
메모리 위치스택에 값 직접 저장변수(주소)는 스택, 실제 데이터는 힙
크기 결정 시점컴파일 타임 (고정)런타임 (동적 할당 가능)
예시int, double, boolean배열, String, 객체

기본형이 더 단순한 이유는 크기가 고정되어 있기 때문이다. 참조형을 쓰는 이유는 크기를 동적으로 결정할 수 있고, 복잡한 데이터 구조를 만들 수 있기 때문이다. 기본형과 참조형에 대한 내용은 객체를 설명할 때 더 자세히 다룬다.


배열 사용 — 인덱스

배열의 각 공간에는 번호가 붙어 있다. 이 번호를 인덱스(index)라 한다. 인덱스는 항상 0부터 시작한다. new int[5]로 5개짜리 배열을 만들면 인덱스는 0, 1, 2, 3, 4다. 마지막 인덱스는 (배열 크기 - 1)이다.

x001 int[] students x001 90 80 70 60 50 [0] [1] [2] [3] [4]
int[] students = new int[5]; // 값 대입 students[0] = 90; students[1] = 80; students[2] = 70; students[3] = 60; students[4] = 50; // 값 읽기 System.out.println("학생1 점수: " + students[0]); // 90 System.out.println("학생5 점수: " + students[4]); // 50

students[0] = 90;이 실행되는 과정을 풀어 쓰면 다음과 같다.

  1. students 변수에서 참조값(x001)을 읽는다.
  2. x001이 가리키는 힙의 배열로 이동한다.
  3. 인덱스 0에 해당하는 공간에 90을 저장한다.

인덱스 범위 초과

인덱스 허용 범위를 벗어나면 런타임에 예외가 발생한다.

students[5] = 100; // ArrayIndexOutOfBoundsException 발생

new int[5]로 만든 배열의 유효 인덱스는 0~4다. students[5]는 존재하지 않는 공간을 참조하므로 런타임에 ArrayIndexOutOfBoundsException이 발생한다. 컴파일은 통과하지만 실행 중에 프로그램이 종료된다.


배열과 for문

배열과 반복문을 결합하면 코드를 크게 줄일 수 있다.

int[] students = {90, 80, 70, 60, 50}; for (int i = 0; i < students.length; i++) { System.out.println("학생" + (i + 1) + " 점수: " + students[i]); }

students.length는 배열의 크기를 반환하는 읽기 전용 속성이다. new int[5]로 만든 배열이라면 5를 반환한다. i < students.length를 조건으로 쓰면 배열 크기가 바뀌어도 반복문 조건을 수정할 필요가 없다.


배열 초기화 단축 문법

배열을 선언과 동시에 값을 넣는 세 가지 방법이 있다.

// 방법 1: 가장 명시적인 방법 int[] students; students = new int[]{90, 80, 70, 60, 50}; // 방법 2: 선언과 동시에 초기화 int[] students = new int[]{90, 80, 70, 60, 50}; // 방법 3: 가장 짧은 방법 (선언과 동시에만 가능) int[] students = {90, 80, 70, 60, 50};

방법 3의 {90, 80, 70, 60, 50}은 배열 변수 선언과 한 줄에 함께 쓸 때만 사용 가능하다. 다음은 컴파일 오류가 발생한다.

int[] students; students = {90, 80, 70, 60, 50}; // 컴파일 오류: 선언과 분리된 상황에서는 new int[] 생략 불가

자바가 내부적으로 {...} 안의 요소 수를 세어 new int[5]를 만들고 값을 채워 넣는다. 동작은 완전히 동일하고 코드만 짧아진다.


향상된 for문 (for-each)

배열을 처음부터 끝까지 순서대로 탐색하는 것이 가장 흔한 패턴이다. 일반 for문으로 작성하면 인덱스 변수, 종료 조건, 인덱스 증가를 모두 작성해야 한다.

// 일반 for문 for (int i = 0; i < numbers.length; ++i) { int number = numbers[i]; System.out.println(number); }

이 패턴을 간결하게 만든 것이 향상된 for문(Enhanced For Loop, for-each문)이다.

// 향상된 for문 (for-each) for (int number : numbers) { System.out.println(number); }

: 오른쪽에 순회할 배열을 놓고, 왼쪽에 매 반복마다 꺼낸 값을 담을 변수를 선언한다. 인덱스를 따로 관리하지 않아도 되고, 범위 초과 실수도 없다.

향상된 for문을 쓸 수 없는 경우

인덱스 값이 필요한 상황에서는 일반 for문을 써야 한다.

// 인덱스가 필요한 경우 — 일반 for문 사용 for (int i = 0; i < numbers.length; ++i) { System.out.println("number " + i + "번의 결과는: " + numbers[i]); }

향상된 for문은 인덱스를 내부에 감춰버리기 때문에 꺼낼 방법이 없다.


2차원 배열

1차원 배열이 값들을 일렬로 나열한 것이라면, 2차원 배열은 행(row)과 열(column)로 구성된 표 형태다.

int[][] arr = new int[2][3]; // 2행 3열

arr[행][열] 순서로 접근한다. 행은 영어로 row(로우), 열은 column(컬럼)이라 한다.

열[0] 열[1] 열[2] 행[0] 행[1] 1 2 3 4 5 6 arr[0][0]~[0][2] arr[1][0]~[1][2]

중첩 for문으로 2차원 배열을 순회하는 패턴이다.

int[][] arr = { {1, 2, 3}, {4, 5, 6} }; for (int row = 0; row < arr.length; row++) { for (int col = 0; col < arr[row].length; col++) { System.out.print(arr[row][col] + " "); } System.out.println(); }

2차원 배열의 length

표현의미위 예시 값
arr.length행의 수2
arr[0].length0번 행의 열 수3
arr[1].length1번 행의 열 수3

2차원 배열은 "배열의 배열"이다. arr[0]{1, 2, 3} 배열 자체를 가리키는 참조값이다. 따라서 arr.length는 행의 수를, arr[row].length는 해당 행의 열 수를 반환한다. 행마다 열 수가 달라도 arr[row].length를 쓰면 안전하게 순회할 수 있다.


배열 복사

배열은 참조형이기 때문에 = 연산자로 복사하면 실제 데이터가 복사되는 것이 아니라 참조값만 복사된다.

int[] a = {1, 2, 3}; int[] b = a; // b는 a와 같은 배열을 참조함 b[0] = 99; System.out.println(a[0]); // 99 출력 — a와 b가 같은 배열을 가리키므로
x001 int[] a x001 int[] b int[] b 99 2 3 [0] [1] [2] a와 b 모두 같은 배열 참조

독립된 복사본을 만들려면 별도의 방법을 사용해야 한다.

방법 1 — for문으로 직접 복사

int[] original = {1, 2, 3, 4, 5}; int[] copy = new int[original.length]; for (int i = 0; i < original.length; i++) { copy[i] = original[i]; }

방법 2 — System.arraycopy()

int[] original = {1, 2, 3, 4, 5}; int[] copy = new int[original.length]; System.arraycopy(original, 0, copy, 0, original.length); // 인자: (원본배열, 원본시작인덱스, 대상배열, 대상시작인덱스, 복사할길이)

System.arraycopy()는 JVM 내부에서 네이티브 코드로 동작해 for문보다 빠르다. 대용량 배열 복사에 사용한다.

방법 3 — Arrays.copyOf()

import java.util.Arrays; int[] original = {1, 2, 3, 4, 5}; int[] copy = Arrays.copyOf(original, original.length);

Arrays.copyOf()는 내부적으로 System.arraycopy()를 호출한다. 코드가 간결하다.

2차원 배열이나 객체 배열에서 위 방법들은 얕은 복사(shallow copy)를 수행한다. 내부 배열은 여전히 같은 참조를 공유한다. 완전히 독립적인 복사본이 필요하면 각 내부 배열도 별도로 복사해야 한다.


정리

개념핵심
배열 선언int[] arr; — 배열을 담을 변수만 만든 것
배열 생성new int[n] — 힙에 n개짜리 공간 생성, 참조값 반환
자동 초기화숫자는 0, boolean은 false, 객체는 null
인덱스0부터 시작, 마지막은 length - 1
length배열 크기를 반환하는 읽기 전용 속성
참조형변수에는 참조값(주소)만 저장, 실제 데이터는 힙
향상된 for문인덱스 불필요할 때 사용, 인덱스 접근이 필요하면 일반 for문
배열 복사=는 참조 복사, 독립 복사는 System.arraycopy() 또는 Arrays.copyOf()

'Study > Java' 카테고리의 다른 글

[Java] 기본형과 참조형  (1) 2026.04.05
[Java] 메소드(method)  (0) 2026.04.05
[Java] 훈련 (입출력 등)  (0) 2026.04.04
[Java] 스코프, 형변환  (0) 2026.04.04
[Java] 반복문  (0) 2026.04.04