Java 19

배열과 반복문, length로 성적 처리하기

배열을 만들었는데 여전히 값을 하나씩 다루면 반복문과 length를 왜 함께 써야 하는지 감이 없다.

근거 · 교안 p129-p133

배열과 반복문, length로 성적 처리하기 대표 이미지

1장: 성적 채점, 수동의 고통: 반복문의 필요성

솔라의 손가락이 키보드 위에서 경쾌하게 춤을 췄다. 친구의 작은 스터디 그룹 성적을 처리하는 프로그램을 만드는 중이었다. 여러 개의 점수를 한 번에 관리하려고 배열을 사용한 자신이 대견했다.

int[] scores = {85, 92, 78, 95, 88};

“좋아, 다섯 명 점수를 배열에 깔끔하게 담았고.”

솔라는 만족스러운 미소를 지으며 중얼거렸다. 이제 남은 건 이 점수들의 합계와 평균을 구하는 일이었다. 간단했다. 배열의 각 칸에 들어있는 값을 인덱스로 하나씩 꺼내 더하면 되니까.

“합계는 sum 변수에 저장하고… scores[0] 더하기, scores[1] 더하기…”

솔라는 곧장 다음 코드를 작성했다.

int sum = scores[0] + scores[1] + scores[2] + scores[3] + scores[4];

코드를 실행하자 결과 창에 합계가 정확히 찍혔다. “완벽해! 그럼 평균은 합계를 학생 수인 5로 나누면 되겠지.”

double average = sum / 5.0;

모든 것이 순조로웠다. 솔라는 의자 등받이에 몸을 기대며 뿌듯한 표정을 지었다. 배열을 쓰니 변수를 여러 개 만들 필요도 없고, 코드도 한눈에 들어왔다.

그때, 조용히 책을 읽던 언니 루나가 솔라의 모니터를 힐끗 보더니 무심하게 말했다.

“그 스터디 그룹, 어제 두 명 더 늘었다던데. 점수는 받았어?”

“어, 정말? 잠시만…”

솔라의 미간이 살짝 찌푸려졌다. 간단한 수정이라고 생각하며 다시 키보드로 손을 옮겼다. 새로 합류한 두 친구의 점수는 91점과 83점. 먼저 배열부터 수정했다.

int[] scores = {85, 92, 78, 95, 88, 91, 83};

배열에 값을 추가하는 건 쉬웠다. 하지만 다음 줄에서 솔라의 손가락이 멈칫했다. 합계를 구하는 코드였다.

int sum = scores[0] + scores[1] + scores[2] + scores[3] + scores[4];

“아…”

짧은 탄식이 흘러나왔다. 뒤에 두 명의 점수를 더 추가해야 했다. 솔라는 한숨을 쉬며 코드를 길게 덧붙였다.

int sum = scores[0] + scores[1] + scores[2] + scores[3] + scores[4] + scores[5] + scores[6];

끝이 아니었다. 평균을 구하는 코드도 학생 수가 바뀌었으니 수정이 필요했다. 솔라는 숫자 5를 지우고 7로 바꿨다.

double average = sum / 7.0;

다시 프로그램을 실행하자 올바른 결과가 나왔지만, 솔라의 표정은 처음과 달리 영 개운치 않았다. 겨우 두 명이 추가되었을 뿐인데, 코드를 세 군데나 고쳐야 했다. 만약 한 명이 그룹을 나가기라도 한다면? 또 배열에서 값을 빼고, 합계 코드에서 해당 인덱스를 지우고, 나누는 숫자까지 바꿔야 할 터였다.

“언니, 이거 좀 이상해. 학생이 늘거나 줄 때마다 내가 코드를 전부 뜯어고쳐야 하잖아. 만약 학생이 50명이면 어떡해? scores[0]부터 scores[49]까지 더하기 기호를 써서 다 나열할 수는 없잖아. 이건… 뭔가 잘못됐어.”

솔라는 자신이 만든 코드가 갑자기 아주 조잡하고 불안정한 구조물처럼 느껴졌다. 작은 변화에도 와르르 무너질 것만 같았다.

루나가 책을 덮고 솔라의 옆으로 다가왔다. 모니터에 길게 늘어선 + scores[...] 코드를 가만히 들여다보았다.

“네가 지금 발견한 게 맞아. 50명, 아니 500명의 점수를 그렇게 손으로 더할 수는 없지.”

루나는 합계를 구하는 코드의 + 기호들을 손가락으로 짚었다.

“이 작업들의 공통점이 뭘까? scores라는 배열의 어떤 값을 가져와서 sum에 더한다. 이 똑같은 구조의 작업이 계속 이어지고 있네.”

“응. 똑같은 일을 계속하고 있어. 배열의 위치만 0, 1, 2, 3… 이렇게 달라지면서.”

솔라가 고개를 끄덕였다. 바로 그 점 때문에 짜증이 났던 것이다.

“그럼 컴퓨터한테 이렇게 시키고 싶지 않아? ‘이 scores 배열에 들어있는 점수들, 처음부터 끝까지 하나씩 꺼내서 sum에 더해줘.’ 라고. 학생 수가 몇 명이든 상관없이 알아서 처리하라고.”

“맞아! 바로 그거야! 내가 일일이 몇 번째 값을 더하라고 지정하는 게 아니라, 그냥 ‘알아서 다 더해’라고 명령하고 싶어.”

솔라의 눈이 반짝였다. 방금 전까지 느꼈던 답답함의 정체가 선명해지는 기분이었다. 수동으로 반복하던 작업에서 벗어나고 싶다는 강렬한 필요성.

“이런 반복적인 작업을 컴퓨터에게 맡기는 방법이 분명히 있을 것 같아.”

솔라의 말에 루나가 조용히 미소 지었다. 솔라는 이제 ‘왜’ 자동화가 필요한지 온몸으로 깨달은 참이었다. 하지만 ‘어떻게’ 그 자동화를 구현할 수 있는지는 아직 알지 못했다. 모니터 속 길게 늘어선 코드는 여전히 솔라가 풀어야 할 숙제로 남아 있었다.

2장: for문으로 배열 요소 쓱싹: 인덱스 기반 순회

솔라의 푸념이 끝나자, 루나는 말없이 키보드를 자기 쪽으로 살짝 끌어당겼다. 솔라가 작성했던 길고 지저분한 덧셈 코드를 지우는 대신, 그 아래에 몇 줄의 공간을 만들었다.

int sum = scores[0] + scores[1] + scores[2] + scores[3] + scores[4] + scores[5] + scores[6];

기존 코드는 그대로 둔 채, 루나는 그 밑에 새로운 구조의 뼈대를 타이핑하기 시작했다. 마치 비교라도 해보라는 듯이.

for (  ;  ;  ) {
    // ...
}

솔라는 모니터에 나타난 낯선 괄호와 세미콜론들을 보며 눈을 가늘게 떴다. for라는 단어는 본 적이 있었다. 정해진 횟수만큼 무언가를 반복시킬 때 쓰는 명령어. 하지만 텅 비어 있는 괄호 안을 보니, 이걸로 어떻게 배열 속 점수들을 ‘알아서’ 더하게 만들 수 있을지 전혀 감이 잡히지 않았다.

“이게… 내가 말한 ‘알아서 다 더해주는’ 그거야?”

솔라가 미심쩍은 목소리로 물었다.

“응. 네가 원하는 자동화의 열쇠가 될 수 있지. 하지만 그 전에, 네가 손으로 썼던 코드를 다시 한번 보자.”

루나는 첫 번째 합계 코드, sum = scores[0] + scores[1] + ... 를 손가락으로 가리켰다.

“이 작업에서 반복되는 부분과, 계속 변하는 부분을 찾아봐.”

“반복되는 건 scores 배열의 값을 sum에 더하는 거고… 변하는 건 저기, 대괄호 [] 안에 들어가는 숫자. 0, 1, 2, 3, 4, 5, 6. 인덱스 번호.”

솔라는 자신의 코드를 분석하며 답했다. 그러자 자기가 느꼈던 불편함의 핵심이 선명해졌다. 바로 저 인덱스 숫자를 하나하나 직접 입력해야만 했던 것이다.

“맞아. 컴퓨터가 저 숫자들을 대신 만들어주면 어떨까? 0부터 시작해서, 1씩 커지면서, 마지막 인덱스인 6까지. 그런 일을 해주는 변수를 저 for 문 안에 만들 수 있어.”

루나는 for 문의 첫 번째 빈칸을 톡톡 건드렸다.

“여기에 인덱스 역할을 할 변수 하나를 만드는 거야. 보통 i라고 많이 써. int i = 0; 이렇게.”

솔라는 루나의 말을 따라 for 문의 첫 번째 칸을 채웠다.

for (int i = 0;  ;  ) { ... }

“좋아. i가 0부터 시작했어. 그럼 언제까지 이 반복을 계속해야 할까? i가 몇이 되면 멈춰야 하지?”

“음… 마지막 인덱스가 6이니까, 6까지는 계속되어야 해. 7이 되면 멈춰야 하고.”

“정확해. ‘i가 7보다 작은 동안’ 계속하라고 조건을 써주는 거야.”

솔라는 두 번째 빈칸에 i < 7;을 입력했다.

for (int i = 0; i < 7;  ) { ... }

“마지막으로, 이 반복문이 한 번 돌 때마다 i는 어떻게 되어야 할까? 0 다음엔 1, 1 다음엔 2가 되어야 하잖아.”

“1씩 커져야지.”

“그걸 i++ 라고 간단히 쓸 수 있어.”

솔라가 마지막 칸까지 채우자, 드디어 for 문의 괄호 안이 완성되었다.

for (int i = 0; i < 7; i++) {
    // ...
}

솔라는 완성된 코드를 가만히 들여다보았다. “0부터 시작해서, 7보다 작은 동안, 1씩 증가하면서…” 이 문장은 이제 인덱스 숫자를 자동으로 만들어내는 작은 기계처럼 보였다.

“그럼 이제 저 안에서 뭘 해야 할지 알겠네.” 솔라의 얼굴에 깨달음이 스쳤다.

“내가 손으로 scores[0], scores[1] 하던 일을, 이제 i가 대신해주면 되잖아!”

솔라는 재빨리 키보드를 가져와 중괄호 {} 안을 채워 넣었다. 기존의 sum 변수를 0으로 다시 초기화하는 것도 잊지 않았다.

int[] scores = {85, 92, 78, 95, 88, 91, 83};
int sum = 0; // 합계를 계산하기 전에 0으로 초기화

for (int i = 0; i < 7; i++) {
    sum = sum + scores[i];
}

System.out.println("최종 합계: " + sum);

엔터 키를 누르자, 콘솔 창에 정확한 합계가 출력되었다. 솔라는 짧은 탄성을 내뱉었다. 아까처럼 scores[0]부터 scores[6]까지 길게 늘어놓지 않았는데도 똑같은 결과가 나왔다. for 문이 정말로 배열의 처음부터 끝까지 모든 요소를 순회하며 값을 더해준 것이다.

“대단하다! scores[i] 라고 썼을 뿐인데… i가 0, 1, 2, 3…으로 계속 바뀌면서 배열의 모든 점수를 알아서 꺼내 온 거네.”

솔라는 for 문이 어떻게 배열과 협력하는지 완벽하게 이해했다. 반복문은 그저 정해진 횟수만 채우는 도구가 아니었다. 배열의 인덱스와 결합될 때, 배열 안의 모든 데이터를 ‘자동으로’ 훑고 지나가는 강력한 탐색기가 되는 것이었다. 길고 지저분했던 수동 코드의 고통이 단 몇 줄의 깔끔한 코드로 해결되는 순간이었다.

“이제 학생이 50명이어도 문제없겠어!” 솔라는 자신만만하게 외쳤다.

그때 루나가 조용히 물었다.

“만약 스터디 그룹에 한 명이 더 들어와서, 학생이 총 8명이 되면 어떡할래? 그 코드에서 뭘 고쳐야 하지?”

솔라의 시선은 망설임 없이 자신이 작성한 for 문으로 향했다. 그리고 한 숫자에 가서 멈췄다.

for (int i = 0; i < 7; i++)

“아…”

자신감 넘치던 솔라의 표정에 다시 작은 균열이 생겼다. 분명 코드는 훨씬 깔끔해졌지만, 여전히 사람의 손길이 필요한 부분이 남아있었다. 학생 수가 바뀔 때마다 저 숫자 7을 직접 고쳐줘야만 했다. 완전한 자동화까지는 아직 한 걸음이 더 남아있는 듯했다.

3장: length로 코드 지킴이: 유연한 반복문 완성

솔라의 자신감 넘치던 표정에 떠오른 작은 균열. 루나의 질문이 그 틈을 파고들었다. “학생이 총 8명이 되면 어떡할래?” 솔라는 자신의 코드를 노려보았다. 반복문은 분명 마법 같았지만, 그 마법의 주문 횟수를 i < 7 이라는 숫자로 직접 지정해 둔 것이 마음에 걸렸다.

“고치면 되지.” 솔라는 스스로를 다독이며 키보드에 손을 올렸다. 먼저 배열에 새 학생의 점수 75를 추가했다. 이제 배열에는 8개의 점수가 들어있다.

int[] scores = {85, 92, 78, 95, 88, 91, 83, 75};

그리고 문제의 for문으로 시선을 옮겼다. 한 명이 늘었으니, 반복 횟수도 하나 늘려야 한다. i < 7i < 8로 바꾸는 건 간단했다.

for (int i = 0; i < 8; i++) {
    sum = sum + scores[i];
}

수정된 코드를 실행하자, 다시 정확한 합계가 출력되었다. 하지만 솔라는 전혀 기쁘지 않았다. 이전의 수동 덧셈 코드를 수정할 때와 똑같은 찝찝함이 남았다. 코드는 짧아졌지만, 근본적인 문제는 해결되지 않았다. 데이터가 바뀔 때마다 개발자가 직접 코드를 수정해야 한다는 사실.

“언니, 이것도 결국 내가 학생 수를 직접 세어서 숫자를 바꿔줘야 하는 거잖아. 학생이 한 명 나간다고 생각해 봐. 그럼 배열에서 점수 지우고, 저 숫자도 7로 다시 바꿔야 해. 완전한 자동화가 아니야.”

솔라의 목소리에는 실망감이 묻어났다. 반복문으로 큰 산을 하나 넘었다고 생각했는데, 바로 앞에 더 교묘한 언덕이 나타난 기분이었다.

루나는 말없이 솔라의 모니터 화면에서 scores 배열을 손가락으로 가리켰다.

“솔라, 저 scores 배열은 자기 자신이 몇 개의 칸을 가지고 있는지 스스로 알고 있을까?”

“응? 배열이… 뭘 안다고?”

솔라는 황당한 표정을 지었다. 배열은 그냥 값을 담아두는 상자나 선반 같은 게 아니었던가? 상자가 자기가 몇 칸짜리인지 어떻게 알겠는가.

“컴퓨터 세상에서는 많은 것들이 자기 자신에 대한 정보를 가지고 있어. 이 배열도 마찬가지야. ‘나는 총 몇 칸짜리 배열입니다’라고 알려주는 특별한 이름표를 달고 다니거든.”

루나는 솔라의 for문 아래에 새로운 출력 코드를 한 줄 추가했다.

System.out.println(scores.length);

.length? 길이라는 뜻이네.”

솔라가 코드를 따라 입력하고 실행 키를 눌렀다. 잠시 후, 콘솔 창에 숫자 8이 나타났다. 솔라는 저도 모르게 “어!” 하고 소리를 냈다. scores 배열에 들어있는 값의 개수와 정확히 일치했다.

호기심이 발동한 솔라는 곧장 실험에 들어갔다. scores 배열에서 점수 두 개를 지워 6개로 만들었다. 그리고 다시 프로그램을 실행했다. 이번에는 6이 출력되었다. 점수를 10개로 늘리자, 10이 찍혔다.

scores.length는 마법처럼 항상 배열의 현재 크기를 정확히 알려주었다. 솔라는 잠시 멍하니 화면을 바라보다가, 무언가 번뜩 스친 듯 고개를 들었다. 시선은 자연스럽게 자신이 계속 고치고 있던 for 문의 조건식으로 향했다.

for (int i = 0; i < 8; i++)

“잠깐만… 설마…”

솔라의 손가락이 바쁘게 움직이기 시작했다. 학생 수를 세어 직접 입력했던 숫자 8을 지우고, 그 자리에 방금 발견한 마법의 이름표, scores.length를 넣었다.

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

이제 for 문은 이렇게 읽혔다. “변수 i는 0부터 시작해서, scores 배열의 길이보다 작은 동안, 1씩 증가하면서 반복해라.”

솔라는 떨리는 마음으로 학생 수를 다시 5명으로 줄여보았다.

int[] scores = {85, 92, 78, 95, 88};

그리고 코드를 실행했다. 반복문 코드는 전혀 건드리지 않았다. 그런데도 결과는 완벽했다. 5개 점수의 합계가 정확히 계산되었다. 이번에는 학생을 10명으로 늘려보았다. 역시나, for 문은 아무 수정 없이 10개의 점수를 모두 더했다.

“됐다! 됐어!”

솔라는 의자에서 벌떡 일어날 뻔했다. scores.length는 단순한 정보가 아니었다. 반복문의 조건과 결합되자, 데이터의 변화에 스스로 반응하는 ‘살아있는 코드’를 만드는 핵심 열쇠였다. 학생이 몇 명이 되든, 더는 반복문의 숫자를 고칠 필요가 없었다.

“이제 진짜 자동화네. 내가 할 일은 그저 scores 배열에 점수를 넣거나 빼는 것뿐이야. 계산은 프로그램이 알아서 다 해줄 테니까.”

솔라는 뿌듯한 미소를 지으며 마지막으로 코드를 한 번 더 다듬었다. 평균을 구하는 부분 역시 sum / 7.0처럼 고정된 숫자를 쓰고 있었다. 솔라는 그 숫자도 망설임 없이 scores.length로 바꿨다.

int[] scores = {85, 92, 78, 95, 88, 91, 83}; // 학생 수는 얼마든지 바뀔 수 있다.
int sum = 0;

// 배열의 길이를 직접 사용해서, 배열 크기가 변해도 코드를 수정할 필요가 없다.
for (int i = 0; i < scores.length; i++) {
    sum = sum + scores[i];
}

// 평균을 계산할 때도 배열의 길이를 사용한다.
double average = (double)sum / scores.length;

System.out.println("총점: " + sum);
System.out.println("평균: " + average);

이제 이 코드는 어떤 변화에도 흔들리지 않는 견고한 성처럼 보였다. 수동으로 값을 더하던 불안정한 오두막집에서 시작해, 반복문이라는 기둥을 세우고, 마침내 length라는 유연한 지붕을 얹어 완성한 것이다. 솔라는 자신이 만든 코드를 보며, 진정한 ‘효율’이란 단순히 코드를 짧게 쓰는 것이 아니라, 변화에 자동으로 대응하는 힘에서 나온다는 것을 깨달았다.