Chapter 07

Java 배열과 인벤토리

여러 인벤토리 슬롯을 하나의 배열로 묶고 인덱스, length, 향상된 for문, 배열 복사를 배웁니다.

난이도
초급
예상 학습
55분
Java 기준
Java 25 LTS
최종 검토

전체 22개 Chapter 확장 구조 중 Chapter 07입니다.

기능 구분

정식 Java 25에서도 쓸 수 있는 이전 버전 기능

Java SE 정식 기능

배열과 인덱스 접근은 Java 25에서도 그대로 쓰는 기존 문법입니다.

이 Chapter의 예제는 Preview 또는 Incubator API 실행 옵션 없이 Java 25 LTS 정식 기능 기준으로 읽을 수 있습니다.

Learning goals

오늘 배울 내용

  • 배열이 여러 변수를 대신하는 이유 이해하기
  • 배열 선언과 초기화하기
  • 인덱스와 length 안전하게 사용하기
  • for와 for-each로 모든 원소 순회하기

배열은 같은 종류의 값을 순서대로 묶습니다

변수를 하나씩 만들면 값이 조금만 늘어도 코드가 금방 복잡해집니다. 인벤토리 슬롯이 네 개일 때 slot1, slot2, slot3, slot4를 만들 수는 있습니다. 하지만 슬롯이 20개가 되면 선언도 길어지고, 전체 출력이나 검색을 하려면 같은 코드를 계속 반복해야 합니다.

배열은 같은 자료형의 값을 순서대로 묶어 하나의 이름으로 다루는 구조입니다. 각 값은 인덱스로 접근합니다. Java 배열은 길이가 정해져 있고, 인덱스는 0부터 시작합니다. 길이가 4인 배열의 인덱스는 0, 1, 2, 3입니다.

String[] inventory = {"Torch", "Bread", "Map", "Rope"};

inventory[0]은 첫 번째 값인 "Torch"이고, inventory[3]은 네 번째 값인 "Rope"입니다. 배열 길이는 inventory.length로 읽습니다. length는 메서드가 아니라 필드이므로 괄호를 붙이지 않습니다.

배열 선언과 초기화

배열을 만드는 방법은 크게 두 가지입니다. 값을 바로 넣어 만들 수도 있고, 길이만 정한 뒤 나중에 각 칸에 값을 넣을 수도 있습니다.

String[] items = {"Torch", "Bread", "Map"};

int[] scores = new int[3];
scores[0] = 10;
scores[1] = 20;
scores[2] = 15;

new int[3]은 정수 세 칸짜리 배열을 만듭니다. 정수 배열의 기본값은 0입니다. boolean 배열은 false, 참조형 배열은 null로 채워집니다. 기본값이 있다는 사실은 편리하지만, 값을 넣는 과정을 잊어도 컴파일 오류가 나지 않을 수 있으므로 주의해야 합니다.

배열 변수 선언은 String[] items처럼 자료형 뒤에 []를 붙이는 형태를 권장합니다. String items[]도 가능하지만, Java 코드에서는 앞쪽 형태가 더 널리 읽힙니다.

인벤토리 전체 출력하기

배열은 반복문과 함께 사용할 때 힘이 납니다.

public class Main {
    public static void main(String[] args) {
        String[] inventory = {"Torch", "Bread", "Map", "Rope"};

        for (int index = 0; index < inventory.length; index++) {
            int slotNumber = index + 1;
            System.out.println(slotNumber + "번 슬롯: " + inventory[index]);
        }
    }
}

예상 출력은 다음과 같습니다.

1번 슬롯: Torch
2번 슬롯: Bread
3번 슬롯: Map
4번 슬롯: Rope

배열 접근에는 0부터 시작하는 index를 사용하고, 사람에게 보여 줄 슬롯 번호는 index + 1로 만듭니다. 반복 조건이 index < inventory.length인 점을 꼭 보세요. 마지막 유효 인덱스는 length - 1이므로 index <= inventory.length를 쓰면 마지막에 존재하지 않는 칸을 읽으려 하게 됩니다.

ArrayIndexOutOfBoundsException 이해하기

배열 범위를 벗어나면 ArrayIndexOutOfBoundsException이 발생합니다.

String[] inventory = {"Torch", "Bread", "Map"};
System.out.println(inventory[3]);

길이가 3인 배열의 유효 인덱스는 0, 1, 2입니다. inventory[3]은 네 번째 칸을 뜻하지만 그런 칸은 없습니다. 외부 입력이나 계산 결과로 인덱스를 만들 때는 항상 범위를 확인해야 합니다.

int selectedIndex = 2;

if (selectedIndex >= 0 && selectedIndex < inventory.length) {
    System.out.println("선택한 아이템: " + inventory[selectedIndex]);
} else {
    System.out.println("없는 슬롯입니다.");
}

조건의 순서도 중요합니다. 인덱스가 0 이상인지, 길이보다 작은지 모두 확인해야 안전합니다. 배열 접근은 조건 검사 뒤에 해야 합니다.

for-each로 모든 값 읽기

인덱스가 필요 없고 값만 차례대로 읽으면 향상된 for문, 흔히 for-each라고 부르는 문법을 사용할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        int[] blockCounts = {12, 5, 18, 9};
        int total = 0;

        for (int count : blockCounts) {
            total += count;
        }

        System.out.println("전체 블록: " + total);
    }
}

예상 출력은 다음과 같습니다.

전체 블록: 44

count에는 반복마다 배열의 다음 값이 들어옵니다. for-each는 읽기 쉽지만 현재 인덱스가 필요하거나 원본 배열의 특정 위치를 바꿔야 할 때는 일반 for문이 더 적합합니다.

원소 수정은 인덱스로 합니다

배열 값을 바꾸려면 인덱스를 사용합니다.

public class Main {
    public static void main(String[] args) {
        int[] durability = {8, 3, 10, 6};

        for (int index = 0; index < durability.length; index++) {
            durability[index] = durability[index] - 1;
        }

        for (int value : durability) {
            System.out.println("내구도: " + value);
        }
    }
}

첫 번째 반복문은 각 칸의 값을 실제로 줄입니다. 두 번째 반복문은 결과를 읽어 출력합니다. 기본형 배열에서 for-each 반복 변수에 새 값을 대입해도 원본 배열은 바뀌지 않습니다.

for (int value : durability) {
    value = value - 1;
}

이 코드는 value라는 임시 변수만 바꿉니다. 원본 배열의 각 칸을 교체하려면 durability[index]처럼 배열과 인덱스를 함께 사용해야 합니다.

배열에서 최댓값 찾기

반복문과 배열을 함께 쓰는 대표 예제는 최댓값, 최솟값, 합계, 평균입니다.

public class Main {
    public static void main(String[] args) {
        int[] scores = {42, 75, 63, 90, 58};

        int max = scores[0];
        int total = 0;

        for (int score : scores) {
            if (score > max) {
                max = score;
            }
            total += score;
        }

        double average = (double) total / scores.length;

        System.out.println("최고 점수: " + max);
        System.out.println("평균 점수: " + average);
    }
}

최댓값을 찾을 때 max를 0으로 시작하는 예제가 많지만, 점수가 음수일 수 있는 데이터라면 틀릴 수 있습니다. 배열이 비어 있지 않다는 전제가 있다면 첫 번째 원소를 기준값으로 시작하는 방법이 안전합니다. 단, 빈 배열에서는 scores[0]도 사용할 수 없으므로 실제 서비스 코드에서는 빈 배열 여부를 먼저 확인해야 합니다.

배열 변수 대입과 복사

배열은 참조형입니다. 배열 변수를 다른 변수에 대입하면 배열 자체가 복제되는 것이 아니라 같은 배열을 함께 가리킵니다.

public class Main {
    public static void main(String[] args) {
        int[] original = {1, 2, 3};
        int[] alias = original;

        alias[0] = 99;

        System.out.println(original[0]);
    }
}

출력은 99입니다. originalalias가 같은 배열을 보고 있기 때문입니다. 별도 사본이 필요하면 java.util.Arrays.copyOf를 사용할 수 있습니다.

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

copy[0] = 99;

System.out.println(original[0]); // 1
System.out.println(copy[0]);     // 99

배열 복사와 참조 공유의 차이는 나중에 객체와 컬렉션을 배울 때도 계속 중요합니다. “변수에 무엇이 들어 있나”보다 “그 변수가 어떤 대상을 가리키나”를 생각하는 습관을 들이세요.

다차원 배열 맛보기

배열의 배열을 만들면 격자 구조를 표현할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        char[][] map = {
            {'S', '.', '.'},
            {'.', '#', '.'},
            {'.', '.', 'G'}
        };

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

예상 출력은 다음과 같습니다.

S . . 
. # . 
. . G 

map[row][col]은 먼저 행 배열을 고르고, 그 안에서 열 값을 고릅니다. 모든 행의 길이가 같을 수도 있지만 Java의 2차원 배열은 실제로 배열의 배열이므로 행마다 길이가 다를 수도 있습니다. 그래서 안쪽 반복 조건은 map[row].length를 사용하는 습관이 좋습니다.

배열과 컬렉션의 선택

배열은 길이가 고정되어 있고 빠르고 단순합니다. 크기가 정해진 데이터, 학습용 예제, 성능이 중요한 내부 처리에서는 여전히 유용합니다. 하지만 항목을 자주 추가하거나 삭제해야 한다면 ArrayList 같은 컬렉션이 더 편합니다. 다음 과정에서 컬렉션을 배우면 배열과 다른 점이 더 선명해집니다.

입문 단계에서는 배열로 인덱스, 반복, 범위 검사, 참조 개념을 익히는 것이 중요합니다. 컬렉션도 내부적으로 배열과 비슷한 구조를 활용하는 경우가 많기 때문에 배열을 이해하면 이후 자료구조 학습이 훨씬 부드러워집니다.

배열 코드를 설계할 때 묻는 질문

배열을 쓰기 전에 먼저 값들이 정말 같은 종류인지 확인하세요. 이름, 체력, 레벨처럼 서로 의미가 다른 값은 배열보다 각각의 변수나 나중에 배울 객체가 더 적합합니다. 반대로 여러 날짜의 온도, 여러 슬롯의 아이템 이름, 여러 시험 점수처럼 같은 기준으로 반복 처리할 값은 배열에 잘 맞습니다.

두 번째 질문은 길이가 언제 정해지는가입니다. 프로그램을 시작할 때 길이가 정해지고 실행 중 거의 바뀌지 않는다면 배열이 단순합니다. 하지만 사용자 입력에 따라 계속 추가되고 삭제된다면 배열만으로 처리하기 번거롭습니다. 배열은 한 번 만든 뒤 길이를 바꿀 수 없기 때문입니다. 새 길이가 필요하면 더 큰 배열을 만들고 기존 값을 복사해야 합니다. 이런 요구가 자주 등장하면 컬렉션을 선택하는 편이 좋습니다.

세 번째 질문은 인덱스가 의미를 갖는가입니다. 배열의 순서가 중요하면 인덱스를 잘 관리해야 합니다. 첫 번째, 마지막, 현재 선택 위치, 다음 위치 같은 개념이 생깁니다. 이때는 0length - 1의 의미를 분명히 해야 합니다. 반대로 순서가 중요하지 않고 포함 여부만 중요하다면 나중에 배울 다른 자료구조가 더 적합할 수 있습니다.

배열과 메서드로 넘어가기 위한 준비

지금은 모든 코드를 main 안에 쓰지만, 배열 처리는 곧 메서드로 나누게 됩니다. 합계를 구하는 코드, 최댓값을 찾는 코드, 안전하게 인덱스를 검사하는 코드는 여러 곳에서 재사용될 수 있습니다. 메서드를 배우면 sum(scores), max(scores), isValidIndex(items, index)처럼 이름 있는 동작으로 분리할 수 있습니다.

배열을 메서드에 전달할 때도 참조 개념이 중요합니다. 메서드가 배열 원소를 바꾸면 호출한 쪽 배열도 바뀔 수 있습니다. 배열 변수 자체를 새 배열로 바꾸는 것과 배열 안의 원소를 바꾸는 것은 다릅니다. 이 차이를 이해하면 객체지향에서 “참조를 전달한다”는 말을 훨씬 쉽게 받아들일 수 있습니다. 실제 입력에서는 길이가 0일 수도 있으므로 최댓값이나 평균을 구하기 전에 빈 배열 여부를 확인하는 습관도 필요합니다.

직접 해보기

  1. 인벤토리에 "Compass""Water"를 추가하고 슬롯 번호가 자동으로 늘어나는지 확인합니다.
  2. int[] temperatures = {18, 21, 20, 19, 22};에서 최솟값과 평균을 구합니다.
  3. 선택 인덱스를 0, 마지막 인덱스, -1, length 값으로 바꾸어 범위 검사 결과를 확인합니다.
  4. Arrays.copyOf로 복사한 배열과 단순 대입한 배열의 차이를 출력해 봅니다.
  5. 3 x 4 char 지도 배열을 만들고 시작점 S와 목표 G를 표시합니다.
평균 힌트

합계는 int로 누적하고, 평균을 계산할 때 (double) total / temperatures.length처럼 한쪽을 double로 변환하면 소수 결과를 얻을 수 있습니다.

배열 복습 체크리스트

배열을 배운 뒤에는 길이와 인덱스의 관계를 즉시 말할 수 있어야 합니다. 길이가 5라면 유효 인덱스는 0부터 4까지입니다. 첫 원소는 [0], 마지막 원소는 [array.length - 1]입니다. 반복 조건은 보통 index < array.length입니다. 이 세 문장을 자연스럽게 말할 수 있으면 배열 경계 오류가 크게 줄어듭니다.

다음 질문에 답해 보세요. 배열의 길이는 만든 뒤 바뀔 수 있나요? for-each는 언제 읽기 좋고, 일반 for문은 언제 필요한가요? 배열 변수를 다른 변수에 대입하면 값이 복사되나요, 같은 배열을 함께 가리키나요? 빈 배열에서 최댓값을 찾으려 하면 어떤 문제가 생기나요?

배열은 이후 자료구조 학습의 출발점입니다. 컬렉션을 쓰더라도 인덱스, 순서, 반복, 복사, 참조의 개념은 계속 등장합니다. 지금 배열에서 겪는 경계값 실수와 참조 공유 실수를 작은 예제로 경험해 두면, 나중에 더 큰 데이터 구조를 다룰 때 훨씬 침착하게 원인을 찾을 수 있습니다.

배열 예제를 확장하는 방향

인벤토리 예제를 더 깊게 연습하려면 검색, 교체, 집계 기능을 추가해 보세요. 특정 아이템 이름이 있는지 찾고, 빈 슬롯을 "Empty"로 표시하고, 내구도 배열의 평균을 계산하는 식입니다. 이 기능들은 모두 반복문과 조건문을 함께 사용합니다.

중요한 점은 한 번에 모든 기능을 넣지 않는 것입니다. 먼저 전체 출력이 되는지 확인하고, 그다음 검색을 추가하고, 마지막으로 교체나 복사를 추가하세요. 작은 단계마다 실행 결과를 확인하면 배열 코드가 훨씬 안정적으로 자랍니다.

이번 장의 정리

배열은 같은 자료형의 여러 값을 순서대로 묶는 구조입니다. 인덱스는 0부터 시작하고, 마지막 인덱스는 length - 1입니다. 모든 값을 읽기만 할 때는 for-each가 간결하고, 위치를 바꾸거나 인덱스가 필요할 때는 일반 for문이 좋습니다. 배열 변수 대입은 복사가 아니라 같은 배열을 가리키는 참조 공유이며, 별도 사본이 필요하면 표준 API로 복사해야 합니다.

FAQ

자주 묻는 질문

배열 인덱스는 왜 0부터 시작하나요?

첫 원소가 배열 시작 위치에서 0칸 떨어져 있다고 보는 방식입니다. 길이가 4라면 마지막 인덱스는 3입니다.

배열 크기를 나중에 늘릴 수 있나요?

한 번 만든 배열의 길이는 바뀌지 않습니다. 크기가 자주 변하면 ArrayList 같은 컬렉션을 사용합니다.

for-each에서 원소를 바꾸면 배열도 바뀌나요?

기본형 배열에서 반복 변수에 새 값을 대입해도 원본 원소는 바뀌지 않습니다. 값을 교체하려면 일반 for와 인덱스를 사용하세요.

작성·편집 ToolPado

콘텐츠 유형 Java 학습 가이드

Java 기준 Java 25 LTS

마지막 검토