Java 배열 이해하기: 1차원 배열, 2차원 배열, for-each까지

Alchemist·2026년 10월 7일

KT AIVLE

목록 보기
66/130

사용자 점수 3개를 저장한다면 변수 세 개로도 가능하다.

int score1 = 80;
int score2 = 90;
int score3 = 75;

하지만 데이터가 100개라면 변수 100개를 만들 수는 없다. 같은 타입의 데이터를 여러 개 관리할 때 사용하는 구조가 배열(array) 이다.

배열 선언과 생성

int[] scores = new int[3];

두 부분으로 나눠보자.

int[] scores;

int 배열을 참조할 변수 scores를 선언한다.

scores = new int[3];

길이가 3인 실제 배열 객체를 생성한다.

배열은 참조형이므로 변수 자체에 모든 요소가 직접 들어가는 것이 아니라 생성된 배열을 가리키는 참조가 저장된다.

배열 요소 기본값

int[] scores = new int[3];

System.out.println(scores[0]);

int 배열의 요소는 기본값 0으로 초기화된다.

타입에 따라 기본값이 다르다.

  • 정수 계열: 0
  • 실수 계열: 0.0
  • boolean: false
  • 참조형: null

지역 변수와 달리 배열의 요소는 생성 시 기본값으로 초기화된다.

인덱스

배열은 0부터 시작한다.

scores[0] = 80;
scores[1] = 90;
scores[2] = 75;

길이가 3인 배열의 인덱스 범위는 0~2다.

scores[3] = 100;

이 코드는 실행 시 ArrayIndexOutOfBoundsException이 발생한다.

배열 초기화 간단히 하기

int[] scores = {80, 90, 75};

값이 이미 정해져 있다면 이 방식이 훨씬 간결하다.

배열과 반복문

배열의 장점은 반복문과 결합했을 때 크게 드러난다.

int[] scores = {80, 90, 75};

for (int i = 0; i < scores.length; i++) {
    System.out.println(scores[i]);
}

배열 길이를 직접 숫자로 작성하지 않고 length를 사용하는 것이 중요하다.

scores.length

배열 크기가 바뀌어도 반복문을 수정할 필요가 없기 때문이다.

배열의 길이는 생성 후 고정된다

int[] scores = new int[3];

이 배열의 길이는 생성 후 3으로 고정된다.

런타임에 길이를 결정할 수는 있다.

int size = scanner.nextInt();
int[] scores = new int[size];

하지만 일단 배열을 생성한 뒤 같은 배열의 길이를 늘리거나 줄일 수는 없다.

데이터 개수가 계속 바뀌어야 한다면 나중에 ArrayList 같은 컬렉션을 사용하게 된다.

향상된 for문

배열의 모든 요소를 처음부터 끝까지 읽기만 한다면 for-each가 편하다.

int[] scores = {80, 90, 75};

for (int score : scores) {
    System.out.println(score);
}

기존 for문보다 인덱스 관련 코드가 줄어든다.

하지만 인덱스 자체가 필요하다면 일반 for문이 더 적합하다.

for (int i = 0; i < scores.length; i++) {
    System.out.println(i + "번째 점수: " + scores[i]);
}

합계와 평균 구하기

int[] responseTimes = {120, 250, 180, 300};
int sum = 0;

for (int responseTime : responseTimes) {
    sum += responseTime;
}

double average = (double) sum / responseTimes.length;

System.out.println("합계: " + sum);
System.out.println("평균: " + average);

배열은 단순히 데이터를 저장하는 데서 끝나지 않고 반복 처리의 기반이 된다.

2차원 배열

행과 열 구조의 데이터를 표현할 때 2차원 배열을 사용할 수 있다.

int[][] visits = {
    {120, 150, 170},
    {90, 110, 130}
};

접근할 때는 두 개의 인덱스를 사용한다.

System.out.println(visits[0][1]);

첫 번째 행의 두 번째 값인 150이 출력된다.

2차원 배열 순회하기

for (int row = 0; row < visits.length; row++) {
    for (int column = 0; column < visits[row].length; column++) {
        System.out.print(visits[row][column] + " ");
    }
    System.out.println();
}

중요한 점은 다음 두 길이가 의미가 다르다는 것이다.

visits.length

행의 개수다.

visits[row].length

해당 행의 열 개수다.

Java의 2차원 배열은 실제로는 배열을 요소로 가지는 배열이므로 행마다 길이가 다를 수도 있다.

int[][] data = {
    {1, 2},
    {3, 4, 5},
    {6}
};

최소값과 최대값 찾기

int[] prices = {32000, 18000, 45000, 27000};

int min = prices[0];
int max = prices[0];

for (int price : prices) {
    if (price < min) {
        min = price;
    }

    if (price > max) {
        max = price;
    }
}

배열을 배우면 반복문과 조건문을 실제 데이터 처리에 연결할 수 있다.

배열 변수에 저장되는 것은 "배열 자체"가 아니다

int[] prices = new int[3];

prices는 참조형 변수다. 실제 요소들이 변수 안에 통째로 들어 있는 것이 아니라, 생성된 배열 객체를 가리키는 참조값(reference value) 이 저장된다.

이 때문에 배열 변수를 다른 변수에 대입하면 요소 전체를 복사하는 것이 아니라 참조값이 복사된다.

int[] a = {10, 20, 30};
int[] b = a;

b[0] = 999;

System.out.println(a[0]); // 999

a와 b가 같은 배열을 참조하기 때문이다. 이 개념은 나중에 객체와 메서드 값 전달을 이해할 때 다시 등장한다.

배열 변수를 그대로 출력하면 메모리 주소가 나오는 걸까?

int[] values = {1, 2, 3};
System.out.println(values);

보통 다음과 비슷한 문자열이 나온다.

[I@6d03e736

이 값을 실제 메모리 주소라고 단정하면 안 된다. 배열이 상속받는 Object.toString() 계열 표현은 클래스 정보와 객체의 identity hash code에 기반한 문자열 표현이며 JVM의 실제 메모리 주소를 보여주는 계약이 아니다.

배열 내용을 확인하려면 다음처럼 출력한다.

System.out.println(java.util.Arrays.toString(values));
[1, 2, 3]

런타임에 크기를 정할 수 있지만 생성 후 길이는 고정이다

int size = scanner.nextInt();
int[] values = new int[size];

사용자 입력을 이용해 실행 중에 배열 길이를 결정할 수 있다. 이것을 보고 "배열 크기가 동적으로 바뀐다"고 오해하기 쉽다.

하지만 new int[size]가 실행되어 하나의 배열이 생성된 뒤에는 그 배열의 length를 바꿀 수 없다.

실행 중 크기 결정 가능 ✅
생성된 배열 자체의 크기 변경 ❌

데이터 개수가 계속 늘고 줄어야 한다면 ArrayList 같은 컬렉션을 배우게 된다.

배열 초기화 문법 차이

다음은 모두 배열을 만드는 코드다.

int[] a = new int[3];
int[] b = new int[]{10, 20, 30};
int[] c = {10, 20, 30};

마지막 축약 문법은 선언과 동시에 사용할 때 특히 편하다.

int[] values;
// values = {10, 20, 30}; // 이런 형태는 사용할 수 없음

values = new int[]{10, 20, 30};

배열은 반복문과 결합할 때 강력해진다

변수 이름이 각각 다르면 반복문으로 접근하기 어렵다.

int user1 = 10;
int user2 = 20;
int user3 = 30;

배열로 묶으면 인덱스만 바꾸며 반복할 수 있다.

int[] users = {10, 20, 30};

for (int i = 0; i < users.length; i++) {
    System.out.println(users[i]);
}

배열의 핵심 학습 포인트는 "여러 데이터를 저장한다"보다 동일한 규칙으로 반복 처리할 수 있게 만든다는 데 있다.

2차원 배열은 실제로 배열의 배열이다

int[][] metrics = new int[2][3];

겉으로는 표처럼 보이지만 Java에서는 각 행이 다시 하나의 배열이다.

그래서 행마다 길이가 달라도 된다.

int[][] logsPerDay = {
    {10, 20},
    {30, 40, 50},
    {60}
};

이 구조에서는 항상 array[0].length가 모든 행의 길이라고 가정하면 안 된다.

for (int row = 0; row < logsPerDay.length; row++) {
    for (int column = 0; column < logsPerDay[row].length; column++) {
        System.out.println(logsPerDay[row][column]);
    }
}

최솟값과 최댓값 초기값을 배열에서 가져오는 이유

다음처럼 임의의 숫자로 시작하면 데이터 범위에 따라 버그가 생길 수 있다.

int min = 0;

모든 데이터가 양수라면 min은 계속 0으로 남는다.

배열이 비어 있지 않다는 전제라면 첫 번째 요소를 초기값으로 쓰는 방식이 안전하다.

int min = values[0];
int max = values[0];

for (int value : values) {
    if (value < min) {
        min = value;
    }

    if (value > max) {
        max = value;
    }
}

배열 기반 프로그램이 커질 때 생기는 한계

상품 이름과 가격을 각각 배열로 관리한다고 해보자.

String[] productNames = new String[10];
int[] productPrices = new int[10];

같은 인덱스끼리 하나의 상품이라는 규칙을 개발자가 직접 지켜야 한다.

productNames[0] ↔ productPrices[0]
productNames[1] ↔ productPrices[1]

데이터가 복잡해질수록 이런 병렬 배열은 관리하기 어렵다. 이후 객체지향을 배우면 이름과 가격을 Product 객체 하나로 묶고, 개수가 유동적이면 ArrayList<Product> 같은 컬렉션으로 확장할 수 있다.

핵심 정리

  • 배열은 같은 타입의 여러 값을 하나의 구조로 관리한다.
  • 배열 인덱스는 0부터 시작한다.
  • 배열 범위를 벗어나면 ArrayIndexOutOfBoundsException이 발생한다.
  • 배열 요소는 생성 시 타입별 기본값으로 초기화된다.
  • 배열의 length를 사용하면 크기에 종속되지 않는 반복 코드를 만들 수 있다.
  • 배열 길이는 생성 시 결정되며 생성 후 변경할 수 없다.
  • 전체 요소 순회에는 향상된 for문이 편리하다.
  • 2차원 배열은 배열 안에 배열이 들어 있는 구조로 이해할 수 있다.
profile
html_programming_language

0개의 댓글