CodeGym /행동 /JAVA 25 SELF /1차원 배열의 기본 연산

1차원 배열의 기본 연산

JAVA 25 SELF
레벨 7, 레슨 2
사용 가능

1. 배열 타입 String

먼저 String 타입 배열에 대해 간단히 이야기해 보겠습니다.

이미 말했듯이, 배열은 어떤 타입이든 될 수 있습니다. 따라서 String 타입의 배열도 만들 수 있습니다. 만약 “키보드로 10개의 문자열을 입력하고 화면에 역순으로 출력하는” 프로그램을 작성해야 한다면 코드는 대략 다음과 같을 것입니다.

String[] array = new String[10];	// 요소 10개의 배열 객체를 생성합니다
for (int i = 0; i < 10; i++)		// 0부터 9까지 반복
{
   array[i] = console.nextLine();	// 키보드에서 문자열을 읽어 배열 칸에 저장합니다
}
for (int i = 9; i >= 0; i--)		// 9부터 0까지 반복
{
    System.out.println(array[i]);	// 배열의 해당 칸을 화면에 출력합니다
}

코드는 거의 달라지지 않았습니다! 배열을 생성할 때 타입 int를 String으로 바꾸기만 하면 됩니다. 그 외에는 완전히 동일합니다!

null 소개

자, 퀴즈입니다 — 이 배열들을 막 생성한 직후 각 칸에는 무엇이 들어 있을까요?

int[] numbers = new int[10];
String[] strings = new String[10];
User[] users = new User[10];

생각해 보면, 아마도 numbers의 칸에는 0이 들어 있겠죠. 맞습니다.

그렇다면 같은 논리로 strings의 칸에는 빈 문자열 — ""이 들어 있어야 할까요? 그럴듯해 보입니다.

그럼 users 배열의 칸에는 무엇이 들어 있을까요? 빈 User 객체들?

바로 여기에 함정이 있습니다. 모든 타입에 “기본값”이 있는 것은 아닙니다. 그래서 Java 설계자들은 특별한 상수 — null을 도입했습니다. null은 빈 참조입니다. 객체 타입의 변수가 생성되면 그 시작 값은 null입니다. 아무 것도 가리키지 않는, 참조의 부재죠.

String name; 	// name에는 null이 들어 있다
name = "Alex";  // name에는 "Alex" 문자열/객체에 대한 참조가 들어간다
name = null; 	// name에는 null이 들어 있다

null 다루기

참조가 null인 경우에는 해당 객체의 메서드를 호출할 수 없습니다 — 객체가 없기 때문이죠! 아래 코드에서는 프로그램이 오류와 함께 종료됩니다:

String name; 	// name에는 null이 들어 있다
String upperName = name.toUpperCase(); // NullPointerException 발생: name == null

아까 그 ‘퀴즈’의 정답은 다음과 같습니다:

int[] numbers = new int[10];		// {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
String[] strings = new String[10];  // {null, null, null, null, null, null, null, null, null, null}
User[] users = new User[10];		// {null, null, null, null, null, null, null, null, null, null}

기본값이 있는 것은 오직 기본형뿐입니다. 나머지 타입의 기본값은 null입니다. String의 시작 값은 빈 문자열이 아닙니다. 그런 법이죠.

2. 메모리에서의 String 타입 배열

그림 1. String 객체가 메모리에 배치되는 방식:

메모리에서의 String 배열

문자열의 텍스트는 변수 안에 직접 저장되지 않습니다. 별도의 메모리 블록이 할당되고, String 타입 변수에는 그 텍스트 객체의 주소(참조)가 저장됩니다.

그림 2. 정수 배열이 메모리에 배치되는 방식:

메모리에서의 int 배열

이 그림은 이미 본 적이 있을 겁니다.

그림 3. 문자열 배열이 메모리에 배치되는 방식:

메모리에서 String 배열이 배치되는 방식

왼쪽에는 String[] 타입의 배열 변수(배열 객체의 주소를 저장)가 있습니다.

가운데에는 String 타입의 배열 객체가 있습니다.

오른쪽에는 어떤 텍스트를 담고 있는 문자열 객체들이 있습니다.

String 타입의 배열 객체 칸에는 문자열(텍스트) 자체가 아니라 그 주소(참조)가 저장됩니다. 이는 String 타입 변수에 문자열(텍스트)의 주소가 저장되는 것과 정확히 같습니다.

3. Java에서 배열 빠른 초기화

배열은 매우 유용하기 때문에 Java 개발자들은 배열 작업을 최대한 편리하게 만들고자 했습니다. 그들이 가장 먼저 한 일은 배열 초기화, 즉 시작 값을 넣는 작업을 단순화하는 것이었습니다.

프로그램이 어디선가 읽어들이는 데이터 외에도, 동작을 위해 자체적인 내부 데이터가 필요한 경우가 자주 있습니다. 예를 들어, 각 달의 일수를 배열에 저장해야 한다고 해 봅시다. 코드는 다음과 같을 수 있습니다:

int[] months = new int[12];
months[0] = 31; // 1월
months[1] = 28; // 2월
months[2] = 31; // 3월
months[3] = 30; // 4월
months[4] = 31; // 5월
months[5] = 30; // 6월
months[6] = 31; // 7월
months[7] = 31; // 8월
months[8] = 30; // 9월
months[9] = 31; // 10월
months[10] = 30; // 11월
months[11] = 31; // 12월

하지만 더 짧게 쓸 수 있는 방법이 있습니다 — Java 설계자들에게 감사하죠:

// 각 달의 일수
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

그냥 배열의 모든 값을 쉼표로 나열하면 됩니다!

컴파일러는 변수-배열의 타입을 보고 컨테이너(배열 객체)의 타입을 추론할 수 있습니다. 그리고 배열 길이는 중괄호 안에 쓴 요소의 개수를 세어 결정합니다.

그래서 이 코드는 더 짧게 쓸 수 있습니다:

// 각 달의 일수
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

정말 멋지지 않나요? 🙂 이런 표기를 “배열의 빠른 초기화”라고 합니다. 참고로 int 타입에만 동작하는 것은 아닙니다...

// 월 이름
String[] months = { "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월" };

4. 루프 for-each

배열의 모든 요소를 순회하는 작업은 너무나 자주 나오기 때문에 이를 위한 전용 루프 — for-each가 있습니다. 표기는 다음과 같습니다:

for (int score : scores)
{
    System.out.println("점수: " + score);
}
for-each로 배열 요소 순회

여기서는 루프가 요소를 직접 순회합니다 — 수동 인덱스가 없습니다. 다만 요소를 직접 변경할 수는 없습니다(루프 변수에는 값의 복사본만 들어 있기 때문). 대신 배열을 단순히 “훑어보기”에는 매우 편리합니다.

컴파일러는 for-each 루프 코드를 아래 코드로 바꿉니다:

for (int i = 0; i < scores.length; i++)
{
    int score = scores[i];
    System.out.println("점수: " + score);
}
for-each가 내부에서 어떻게 동작하는지

그러니 마법 같은 건 없습니다. 이제 왜 score 변수를 통해 배열 요소를 변경할 수 없는지 이해될 것입니다.

예제를 하나 더 써 봅시다: 모든 점수의 합을 구해 보겠습니다.

int sum = 0;
for (int score : scores)
{
    sum += score;
}
System.out.println("모든 점수의 합: " + sum);
for-each로 배열 값의 합 구하기

어떤 반복문을 언제 사용할까?

  • 인덱스가 필요하거나 요소를 변경해야 한다면 — for 루프.
  • 값들을 그냥 순회하기만 하면 된다면 — for-each가 더 빠르고 깔끔합니다.

5. 배열 요소 변경

만약 각 점수를 1점씩 올려야 한다면(예: 모범 행동 보너스) 어떻게 할까요? 이때는 인덱스가 있는 고전적인 for를 사용해야 합니다. 그래야 배열의 값을 변경할 수 있습니다.

예: 각 요소를 1씩 증가

for (int i = 0; i < grades.length; i++) {
    grades[i] = grades[i] + 1;
}

이 코드를 실행한 뒤에는 grades 배열에 새로운 값이 들어갑니다.

왜 for-each로는 이렇게 할 수 없을까?

for (int grade : grades) {
    grade = grade + 1; // 동작하지 않습니다!
}

이 코드는 배열을 변경하지 못합니다. 왜냐하면 grade는 배열 값의 복사본일 뿐, 요소 자체에 대한 참조가 아니기 때문입니다.

6. 배열을 이용한 계산

배열은 종종 계산에 사용됩니다: 합계, 평균, 최댓값, 최솟값 등.

예: 배열의 모든 요소 합

int sum = 0;
for (int i = 0; i < grades.length; i++) {
    sum += grades[i]; // sum = sum + grades[i]; 와 동일
}
System.out.println("점수 합계: " + sum);

예: 최댓값 찾기

int max = grades[0]; // 첫 요소부터 시작
for (int i = 1; i < grades.length; i++) {
    if (grades[i] > max) {
        max = grades[i];
    }
}
System.out.println("최고 점수: " + max);

예: 최솟값 찾기

int min = grades[0]; // 첫 요소부터 시작
for (int i = 1; i < grades.length; i++) {
    if (grades[i] < min) {
        min = grades[i];
    }
}
System.out.println("최저 점수: " + min);

예: 평균값 계산

int sum = 0;
for (int i = 0; i < grades.length; i++) {
    sum += grades[i];
}
double average = (double) sum / grades.length; // 반드시 double로 형변환!
System.out.println("평균 점수: " + average);

주의: 정수가 아닌 실수를 얻으려면, 나누기 전에 합계를 double로 명시적으로 변환해야 합니다.

7. 실전 과제

키보드로 배열 입력

사용자가 입력한 값으로 배열을 채워야 할 때가 자주 있습니다. 이를 위해 Scanner와 루프를 사용합니다.

Scanner console = new Scanner(System.in);

int n = 5; // 배열 크기
int[] numbers = new int[n];

System.out.println("정수 " + n + "개를 입력하세요:");
for (int i = 0; i < n; i++) {
    numbers[i] = console.nextInt();
}

System.out.println("입력한 값:");
for (int i = 0; i < n; i++) {
    System.out.println(numbers[i]);
}

배열을 역순으로 출력

때로는 배열의 끝에서부터 요소를 출력해야 할 때가 있습니다(예: 누가 마지막으로 방에 들어왔는지 확인).

for (int i = grades.length - 1; i >= 0; i--) {
    System.out.println("점수 No. " + (i + 1) + ": " + grades[i]);
}

8. 1차원 배열 작업 시 흔한 실수

오류 №1: 배열 범위를 벗어남

가장 흔한 문제는 존재하지 않는 요소에 접근하려는 시도입니다. Java에서는 ArrayIndexOutOfBoundsException 예외가 발생합니다. 기억하세요: 마지막으로 허용되는 인덱스는 arr.length - 1입니다.

int[] arr = new int[5];
System.out.println(arr[5]); // 오류! 인덱스는 0부터 4까지.

오류 №2: 배열 초기화를 잊음

변수만 선언하고, 배열을 생성하지 않았습니다:

int[] arr;
arr[0] = 5; // 오류! 배열이 생성되지 않았습니다.

new를 사용해 반드시 배열을 생성해야 합니다:

arr = new int[10];

오류 №3: for-each로 배열 요소를 변경하려 함

for-each 루프의 변수는 요소에 대한 참조가 아니라 값의 복사본입니다:

for (int x : arr) {
    x = 100; // 배열이 바뀌지 않습니다!
}

값을 변경하려면 인덱스가 있는 일반 for를 사용하세요.

오류 №4: 배열 길이를 잘못 사용

가끔 배열의 길이와 마지막 인덱스를 혼동합니다:

for (int i = 0; i <= arr.length; i++) { // 오류! i < arr.length 이어야 합니다
    // ...
}

이 코드는 범위를 벗어나게 됩니다.

오류 №5: 암시적 형 변환

int 타입 배열에는 double 값을 직접 대입할 수 없습니다:

int[] arr = new int[3];
arr[0] = 3.14; // 오류! 3.14는 double입니다.
1
과제
JAVA 25 SELF, 레벨 7, 레슨 2
잠금
하이스코어 테이블 초기 설정 🚀
하이스코어 테이블 초기 설정 🚀
1
과제
JAVA 25 SELF, 레벨 7, 레슨 2
잠금
좋아하는 영화 목록을 역순으로 🎞️
좋아하는 영화 목록을 역순으로 🎞️
1
과제
JAVA 25 SELF, 레벨 7, 레슨 2
잠금
장바구니 총 구매액 계산 🛍️
장바구니 총 구매액 계산 🛍️
1
과제
JAVA 25 SELF, 레벨 7, 레슨 2
잠금
하루 최고 온도 확인 🌡️
하루 최고 온도 확인 🌡️
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION