for 반복과 중첩 반복
시작값·조건·변화를 한 줄에 모은 for와 향상된 for를 익히고, 반복 안에 반복을 넣었을 때 본문이 몇 번 실행되는지와 for·while을 고르는 기준을 배웁니다.
앞 글의 while로 반복은 모두 표현할 수 있습니다. 하지만 '1부터 3까지'처럼 정해진 횟수만큼 도는 반복을 while로 쓰면, 카운터를 선언하는 줄은 반복문 위에, 계속할 조건은 while 괄호 안에, 카운터를 늘리는 줄은 본문 맨 아래에 흩어집니다. 본문이 길어지면 카운터가 어디서 시작해 어떻게 변하는지 한눈에 보이지 않고, 증가 줄을 빠뜨리거나 continue 아래에 두는 실수도 생깁니다.
for는 이 세 가지를 반복문 머리 한 줄에 모읍니다. 이 글에서는 for가 어떤 순서로 실행되는지 따라가 보고, 조건 칸을 비운 for, 반복 안에 반복을 넣는 중첩 반복, 그리고 카운터 없이 입력을 하나씩 꺼내는 향상된 for를 차례로 배웁니다. 마지막에는 같은 반복을 for와 while 중 무엇으로 쓸지 고르는 기준을 정리합니다.
for의 세 칸
다음 프로그램은 회원 번호 1, 2, 3을 차례로 출력합니다. 앞 글의 while에서는 카운터의 시작값, 계속할 조건, 값의 변화를 서로 다른 줄에 적었는데, 이 코드에서는 셋이 모두 for 다음 괄호 한 줄에 들어 있습니다.
public class ForRange {
public static void main(String[] args) {
for (int memberNumber = 1; memberNumber <= 3; memberNumber++) {
System.out.println("member=" + memberNumber);
}
}
}member=1
member=2
member=3for 다음 괄호 안은 세미콜론으로 나뉜 세 칸입니다. 첫 칸 int memberNumber = 1은 카운터를 만들고 시작값을 정하는 초기화이고, 둘째 칸 memberNumber <= 3은 반복을 계속할 조건입니다. 셋째 칸 memberNumber++는 회차가 끝날 때마다 카운터를 바꾸는 증감식입니다. 앞 글에서 while이 제대로 끝나려면 시작값, 조건, 변화가 맞물려야 한다고 했는데, for는 그 셋을 한곳에 적게 만든 문법입니다.
for가 실행되는 순서
세 칸이 한 줄에 나란히 적혀 있지만, 실행되는 순서는 적힌 순서와 다릅니다. 아래 코드에서 셋째 칸의 memberNumber++는 본문보다 앞에 적혀 있는데, 실제로 본문보다 먼저 실행되는지 눈여겨보세요.
for (int memberNumber = 1; memberNumber <= 3; memberNumber++) {
System.out.println(memberNumber);
}- 초기화
int memberNumber = 1을 실행합니다. 반복 전체에서 단 한 번입니다. - 조건
memberNumber <= 3을 검사합니다. 거짓이면 반복을 끝냅니다. - 본문을 실행합니다.
- 증감식
memberNumber++를 실행합니다. - 2번으로 돌아가 조건을 다시 검사합니다.
증감식은 괄호 안에서는 조건 옆에 있지만, 실제로는 본문이 끝난 뒤에 실행됩니다. 그래서 첫 회차의 본문은 시작값 1을 그대로 출력합니다. 회차마다 값을 적어 보면 다음과 같습니다.
| 조건 검사 | memberNumber | 조건 | 본문 출력 | 증감 뒤 |
|---|---|---|---|---|
| 1번째 | 1 | 참 | 1 | 2 |
| 2번째 | 2 | 참 | 2 | 3 |
| 3번째 | 3 | 참 | 3 | 4 |
| 4번째 | 4 | 거짓 | (반복 종료) |
memberNumber가 4가 된 뒤 조건을 한 번 더 검사하고서야 반복이 끝납니다. 시작값이 처음부터 조건을 만족하지 않으면, 예를 들어 memberNumber = 5로 시작하면 본문과 증감식은 한 번도 실행되지 않습니다. 조건을 본문보다 먼저 검사한다는 점에서 for는 do-while이 아니라 while과 같습니다. 아래 그림은 이 순서를 한 단계씩 보여 주고, 반복이 끝난 뒤에는 카운터를 쓸 수 없다는 점도 함께 표시합니다.
for 한 회차의 실행 순서와 카운터 수명
초기화 1회 뒤 조건·본문·증가·재검사로 1~3을 출력한 후 4에서 종료합니다. 카운터의 상태와 스코프 종료를 단계로 추적합니다.
FLOWCHART · ITERATION ORDER
for 한 회차의 실행 순서와 카운터 수명
초기화는 한 번만 실행된다. 이후에는 조건이 참인 동안 본문과 증가가 이어지고, 조건이 거짓이 되는 순간 반복 블록과 카운터의 접근 범위가 함께 끝난다.
한 번 초기화한 뒤 조건 → 본문 → 증가 → 조건 순서로 반복합니다.
초기화 · 한 번
int memberNumber = 1을 for에 들어갈 때 한 번만 실행합니다.조건 · 본문보다 먼저
memberNumber <= 3이 참일 때만 본문으로 갑니다. 첫 검사부터 거짓이면 본문과 증가는 0회입니다.본문 · 1, 2, 3 출력
각 회차의 현재
memberNumber를 출력합니다.증가 · 다시 조건으로
memberNumber++로 2, 3, 4를 만든 뒤 같은 조건을 재검사합니다.종료 · 4에서 false
조건이 거짓이면 반복을 끝냅니다. for 헤더에서 선언한 카운터는 헤더와 반복 블록 안에서만 접근할 수 있습니다.
- 안쪽
- for 헤더의 조건·증가와 반복 본문에서
memberNumber를 사용할 수 있습니다. - 종료 뒤
- 반복 블록을 벗어나면
memberNumber는 스코프 밖이라 접근할 수 없습니다.
- true이면 본문으로
- false이면 즉시 종료
- 카운터 접근 가능 범위
결과가 1, 2, 3인 까닭은 증가가 본문 뒤에 오고, 4가 된 뒤 조건을 한 번 더 검사하기 때문이다.
이 예의 조건
- 헤더에서 선언한 memberNumber는 반복 뒤 스코프 밖입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | for 한 회차의 실행 순서와 카운터 수명 for 초기화, 조건, 본문, 증가, 재검사와 카운터 스코프의 실행 흐름 FLOWCHART · ITERATION ORDER for 한 회차의 실행 순서와 카운터 수명 초기화는 한 번만 실행된다. 이후에는 조건이 참인 동안 본문과 증가가 이어지고, 조건이 거짓이 되는 순간 반복 블록과 카운터의 접근 범위가 함께 끝난다. 한 번 초기화한 뒤 조건 → 본문 → 증가 → 조건 순서로 반복합니다. 초기화 · 한 번 int memberNumber = 1 을 for에 들어갈 때 한 번만 실행합니다. 조건 · 본문보다 먼저 memberNumber <= 3 이 참일 때만 본문으로 갑니다. 첫 검사부터 거짓이면 본문과 증가는 0회입니다. 본문 · 1, 2, 3 출력 각 회차의 현재 memberNumber 를 출력합니다. 증가 · 다시 조건으로 memberNumber++ 로 2, 3, 4를 만든 뒤 같은 조건을 재검사합니다. 종료 · 4에서 false 조건이 거짓이면 반복을 끝냅니다. for 헤더에서 선언한 카운터는 헤더와 반복 블록 안에서만 접근할 수 있습니다. 안쪽 for 헤더의 조건·증가와 반복 본문에서 memberNumber 를 사용할 수 있습니다. 종료 뒤 반복 블록을 벗어나면 memberNumber 는 스코프 밖이라 접근할 수 없습니다. true이면 본문으로 false이면 즉시 종료 카운터 접근 가능 범위 결과가 1, 2, 3인 까닭은 증가가 본문 뒤에 오고, 4가 된 뒤 조건을 한 번 더 검사하기 때문이다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 초기화 memberNumber=1을 한 번 설정하고 조건을 검사합니다. |
|
|
| 회차 진전 본문 출력 후 memberNumber++하고 다시 검사합니다. |
|
|
| 종료와 수명 4≤3이 false여서 반복을 끝냅니다. |
|
|
for 한 회차의 실행 순서와 카운터 수명
초기화 1회 뒤 조건·본문·증가·재검사로 1~3을 출력한 후 4에서 종료합니다. 카운터의 상태와 스코프 종료를 단계로 추적합니다.
FLOWCHART · ITERATION ORDER
for 한 회차의 실행 순서와 카운터 수명
초기화는 한 번만 실행된다. 이후에는 조건이 참인 동안 본문과 증가가 이어지고, 조건이 거짓이 되는 순간 반복 블록과 카운터의 접근 범위가 함께 끝난다.
한 번 초기화한 뒤 조건 → 본문 → 증가 → 조건 순서로 반복합니다.
초기화 · 한 번
int memberNumber = 1을 for에 들어갈 때 한 번만 실행합니다.조건 · 본문보다 먼저
memberNumber <= 3이 참일 때만 본문으로 갑니다. 첫 검사부터 거짓이면 본문과 증가는 0회입니다.본문 · 1, 2, 3 출력
각 회차의 현재
memberNumber를 출력합니다.증가 · 다시 조건으로
memberNumber++로 2, 3, 4를 만든 뒤 같은 조건을 재검사합니다.종료 · 4에서 false
조건이 거짓이면 반복을 끝냅니다. for 헤더에서 선언한 카운터는 헤더와 반복 블록 안에서만 접근할 수 있습니다.
- 안쪽
- for 헤더의 조건·증가와 반복 본문에서
memberNumber를 사용할 수 있습니다. - 종료 뒤
- 반복 블록을 벗어나면
memberNumber는 스코프 밖이라 접근할 수 없습니다.
- true이면 본문으로
- false이면 즉시 종료
- 카운터 접근 가능 범위
결과가 1, 2, 3인 까닭은 증가가 본문 뒤에 오고, 4가 된 뒤 조건을 한 번 더 검사하기 때문이다.
이 예의 조건
- 헤더에서 선언한 memberNumber는 반복 뒤 스코프 밖입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | for 한 회차의 실행 순서와 카운터 수명 for 초기화, 조건, 본문, 증가, 재검사와 카운터 스코프의 실행 흐름 FLOWCHART · ITERATION ORDER for 한 회차의 실행 순서와 카운터 수명 초기화는 한 번만 실행된다. 이후에는 조건이 참인 동안 본문과 증가가 이어지고, 조건이 거짓이 되는 순간 반복 블록과 카운터의 접근 범위가 함께 끝난다. 한 번 초기화한 뒤 조건 → 본문 → 증가 → 조건 순서로 반복합니다. 초기화 · 한 번 int memberNumber = 1 을 for에 들어갈 때 한 번만 실행합니다. 조건 · 본문보다 먼저 memberNumber <= 3 이 참일 때만 본문으로 갑니다. 첫 검사부터 거짓이면 본문과 증가는 0회입니다. 본문 · 1, 2, 3 출력 각 회차의 현재 memberNumber 를 출력합니다. 증가 · 다시 조건으로 memberNumber++ 로 2, 3, 4를 만든 뒤 같은 조건을 재검사합니다. 종료 · 4에서 false 조건이 거짓이면 반복을 끝냅니다. for 헤더에서 선언한 카운터는 헤더와 반복 블록 안에서만 접근할 수 있습니다. 안쪽 for 헤더의 조건·증가와 반복 본문에서 memberNumber 를 사용할 수 있습니다. 종료 뒤 반복 블록을 벗어나면 memberNumber 는 스코프 밖이라 접근할 수 없습니다. true이면 본문으로 false이면 즉시 종료 카운터 접근 가능 범위 결과가 1, 2, 3인 까닭은 증가가 본문 뒤에 오고, 4가 된 뒤 조건을 한 번 더 검사하기 때문이다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 초기화 memberNumber=1을 한 번 설정하고 조건을 검사합니다. |
|
|
| 회차 진전 본문 출력 후 memberNumber++하고 다시 검사합니다. |
|
|
| 종료와 수명 4≤3이 false여서 반복을 끝냅니다. |
|
|
같은 반복을 while로 쓰면
위 for를 while로 옮기면 다음과 같습니다. for 머리의 조건은 while의 괄호로 그대로 옮겨 가고, 셋째 칸의 증감식 memberNumber++는 본문의 마지막 문장이 됩니다.
int memberNumber = 1;
while (memberNumber <= 3) {
System.out.println(memberNumber);
memberNumber++;
}동작은 같지만 두 가지가 다릅니다. 첫째, 카운터 선언이 반복문 바깥으로 나옵니다. 그래서 반복이 끝난 뒤에도 memberNumber가 남아 4라는 값을 들고 있습니다. 반면 for 머리에서 선언한 카운터는 for 안에서만 쓸 수 있고, 반복이 끝난 뒤 이름을 쓰면 cannot find symbol 컴파일 오류가 납니다. 반복이 끝난 뒤에는 필요 없는 변수가 남지 않으니, 나중에 실수로 그 값을 다시 쓸 일도 없습니다. 변수를 쓸 수 있는 범위는 변수 스코프와 숫자 형변환에서 자세히 다룹니다.
둘째, continue가 다르게 동작합니다. for의 continue는 본문의 나머지를 건너뛴 뒤 증감식을 실행하고 조건 검사로 갑니다. 그래서 앞 글에서 본 'continue가 증가를 건너뛰어 같은 값에 갇히는' 실수가 for에서는 일어나지 않습니다. 다음 코드는 앞 글의 while에서는 2에 갇혔던 반복을 for로 쓴 것입니다.
for (int value = 1; value < 8; value++) {
if (value % 2 == 0) {
continue;
}
System.out.println(value);
}이 반복은 1, 3, 5, 7을 출력하고 정상적으로 끝납니다. 짝수에서 continue가 실행되어도 증감식 value++는 그대로 실행되기 때문입니다.
조건 칸을 비운 for
for의 세 칸은 각각 비워 둘 수 있습니다. 세 칸을 모두 비운 for (;;)도 올바른 문법입니다.
for (;;) {
// break가 없다면 끝없이 실행됩니다
}조건 칸이 비어 있으면 Java는 조건을 늘 참으로 취급합니다. 그래서 for (;;)는 앞 글의 while (true)와 같은 무한 반복이고, 끝내려면 본문에서 break나 return을 실행해야 합니다.
조건 칸만 비우는 형태는 카운터는 일정하게 늘리지만 언제 멈출지는 본문에서 계산한 결과로 정해야 할 때 씁니다. 다음 프로그램은 1부터 차례로 더해 가다가 합계가 15 이상이 되면 멈춥니다.
public final class ForWithBreak {
public static void main(String[] args) {
int sum = 0;
for (int number = 1; ; number++) {
sum += number;
if (sum >= 15) {
System.out.println("number=" + number);
break;
}
}
System.out.println("sum=" + sum);
}
}number=5
sum=15합계는 1, 3, 6, 10, 15로 늘어나고, number가 5일 때 15가 되어 break가 실행됩니다. 카운터의 시작과 변화는 for 머리에 있고, 멈추는 조건은 본문의 if에 있습니다. 조건 칸을 비운 이유를 본문의 break가 보여 주는 셈입니다. 반대로 '5까지'처럼 끝을 미리 알고 있다면 number <= 5를 조건 칸에 쓰는 편이 낫습니다. 반복이 언제 끝나는지 머리만 읽어도 알 수 있기 때문입니다.
반복 안의 반복
반복문 본문에 또 반복문을 넣을 수 있습니다. 이를 중첩 반복(nested loop)이라고 합니다. 다음 프로그램은 가입 요청 묶음(batch) 2개마다 처리 칸(slot) 3개를 출력합니다.
public final class MemberGrid {
public static void main(String[] args) {
int printed = 0;
for (int batch = 1; batch <= 2; batch++) {
for (int slot = 1; slot <= 3; slot++) {
printed++;
System.out.println("batch=" + batch + ", slot=" + slot);
}
}
System.out.println("printed=" + printed);
}
}batch=1, slot=1
batch=1, slot=2
batch=1, slot=3
batch=2, slot=1
batch=2, slot=2
batch=2, slot=3
printed=6바깥 반복이 한 회차를 시작할 때마다 안쪽 for는 초기화 slot = 1부터 다시 실행됩니다. 그래서 batch가 1인 동안 slot이 1, 2, 3으로 한 바퀴를 다 돌고, 안쪽 반복이 끝나야 바깥 반복이 batch++를 실행합니다. batch가 2가 되면 slot은 다시 1부터 시작합니다. 출력에서 batch는 세 줄마다 한 번씩 바뀌고 slot은 줄마다 바뀌는 이유입니다.
본문은 바깥 2회 × 안쪽 3회 = 6번 실행되었고, 마지막 줄 printed=6이 그 횟수를 보여 줍니다. 일반적으로 바깥 반복이 n번, 안쪽 반복이 m번 돌면 본문은 n × m번 실행됩니다. 두 횟수가 모두 입력 크기에 따라 늘어난다면 작업량은 곱으로 커집니다. 입력이 1,000개일 때 1,000 × 1,000 = 1,000,000번이 되는 식입니다. 그래서 중첩 반복은 격자를 채우거나 모든 조합을 확인하는 것처럼 정말로 모든 쌍이 필요할 때만 씁니다.
아래 그림은 2 × 3 격자의 각 칸이 본문 한 번의 실행이라는 것을 보여 줍니다. 안쪽 반복에서 쓴 continue와 break가 어디까지 영향을 주는지도 함께 표시되어 있는데, 이 부분은 바로 다음 절에서 코드로 확인합니다.
중첩 for의 2×3 실행 순서와 제어 범위
batch 2회마다 slot 3회가 처음부터 실행돼 여섯 격자 칸을 방문합니다. 고정 격자에 현재 batch/slot만 옮기면 안쪽 재시작과 break 범위가 보입니다.
SEMANTIC TABLE · NESTED ITERATION
중첩 for의 2×3 실행 순서와 제어 범위
행은 바깥 batch, 열은 그 안의 slot이다. 두 행과 세 열의 교차점이 정확히 여섯 번 실행되며, 안쪽 continue와 break는 현재 안쪽 반복만 제어한다.
| 바깥 반복 · batch | 안쪽 slot 1 | 안쪽 slot 2 | 안쪽 slot 3 |
|---|---|---|---|
| batch 1 | 전체 순서 1 · (1, 1) |
전체 순서 2 · (1, 2) |
전체 순서 3 · (1, 3) |
| batch 2 | 전체 순서 4 · (2, 1) |
전체 순서 5 · (2, 2) |
전체 순서 6 · (2, 3) |
- 2 × 3 = 6회
- 바깥 반복 2회마다 안쪽 반복 3회가 완주하면 본문은 모두 6회 실행됩니다.
- 안쪽 continue
- 현재 slot의 남은 본문만 건너뛰고 같은 batch의 다음 slot으로 갑니다.
- 안쪽 break
- 현재 batch의 남은 slot을 끝내고 바깥 for의 다음 batch로 갑니다.
2 batch × 3 slot 본문 실행은 전체 6회입니다.
batch 1 · 전체 순서 1–3
- slot 1
(1, 1)· 전체 순서 1 - slot 2
(1, 2)· 전체 순서 2 - slot 3
(1, 3)· 전체 순서 3
batch 2 · 전체 순서 4–6
- slot 1
(2, 1)· 전체 순서 4 - slot 2
(2, 2)· 전체 순서 5 - slot 3
(2, 3)· 전체 순서 6
- 안쪽 continue
- 현재 slot의 나머지만 건너뛰며 같은 batch의 다음 slot은 계속 실행합니다.
- 안쪽 break
- 현재 batch의 안쪽 for만 끝내며 바깥 for는 다음 batch를 시작합니다.
행 header가 바깥 batch를, 열 header가 그 안의 slot을 나타낸다. 제어문 앞의 “안쪽”이 어느 반복을 건너뛰거나 끝내는지 결정한다.
이 예의 조건
- batch 두 회, 각 batch의 slot 세 회입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | 중첩 for의 2×3 실행 순서와 제어 범위 바깥 batch와 안쪽 slot의 2×3 실행 순서 및 continue와 break의 적용 범위 SEMANTIC TABLE · NESTED ITERATION 중첩 for의 2×3 실행 순서와 제어 범위 행은 바깥 batch, 열은 그 안의 slot이다. 두 행과 세 열의 교차점이 정확히 여섯 번 실행되며, 안쪽 continue와 break는 현재 안쪽 반복만 제어한다. 바깥 batch별 안쪽 slot과 전체 실행 순서 바깥 반복 · batch 안쪽 slot 1 안쪽 slot 2 안쪽 slot 3 batch 1 전체 순서 1 · (1, 1) 전체 순서 2 · (1, 2) 전체 순서 3 · (1, 3) batch 2 전체 순서 4 · (2, 1) 전체 순서 5 · (2, 2) 전체 순서 6 · (2, 3) 2 × 3 = 6회 바깥 반복 2회마다 안쪽 반복 3회가 완주하면 본문은 모두 6회 실행됩니다. 안쪽 continue 현재 slot의 남은 본문만 건너뛰고 같은 batch의 다음 slot으로 갑니다. 안쪽 break 현재 batch의 남은 slot을 끝내고 바깥 for의 다음 batch로 갑니다. 2 batch × 3 slot 본문 실행은 전체 6회입니다. batch 1 · 전체 순서 1–3 slot 1 (1, 1) · 전체 순서 1 slot 2 (1, 2) · 전체 순서 2 slot 3 (1, 3) · 전체 순서 3 batch 2 · 전체 순서 4–6 slot 1 (2, 1) · 전체 순서 4 slot 2 (2, 2) · 전체 순서 5 slot 3 (2, 3) · 전체 순서 6 안쪽 continue 현재 slot의 나머지만 건너뛰며 같은 batch의 다음 slot은 계속 실행합니다. 안쪽 break 현재 batch의 안쪽 for만 끝내며 바깥 for는 다음 batch를 시작합니다. 행 header가 바깥 batch를, 열 header가 그 안의 slot을 나타낸다. 제어문 앞의 “안쪽”이 어느 반복을 건너뛰거나 끝내는지 결정한다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 batch batch 1에서 안쪽 slot 반복을 시작합니다. |
|
|
| 다음 batch batch 2에서 안쪽 반복을 새로 실행합니다. |
|
|
| 실행 횟수 두 batch의 본문 실행을 합칩니다. |
|
|
중첩 for의 2×3 실행 순서와 제어 범위
batch 2회마다 slot 3회가 처음부터 실행돼 여섯 격자 칸을 방문합니다. 고정 격자에 현재 batch/slot만 옮기면 안쪽 재시작과 break 범위가 보입니다.
SEMANTIC TABLE · NESTED ITERATION
중첩 for의 2×3 실행 순서와 제어 범위
행은 바깥 batch, 열은 그 안의 slot이다. 두 행과 세 열의 교차점이 정확히 여섯 번 실행되며, 안쪽 continue와 break는 현재 안쪽 반복만 제어한다.
| 바깥 반복 · batch | 안쪽 slot 1 | 안쪽 slot 2 | 안쪽 slot 3 |
|---|---|---|---|
| batch 1 | 전체 순서 1 · (1, 1) |
전체 순서 2 · (1, 2) |
전체 순서 3 · (1, 3) |
| batch 2 | 전체 순서 4 · (2, 1) |
전체 순서 5 · (2, 2) |
전체 순서 6 · (2, 3) |
- 2 × 3 = 6회
- 바깥 반복 2회마다 안쪽 반복 3회가 완주하면 본문은 모두 6회 실행됩니다.
- 안쪽 continue
- 현재 slot의 남은 본문만 건너뛰고 같은 batch의 다음 slot으로 갑니다.
- 안쪽 break
- 현재 batch의 남은 slot을 끝내고 바깥 for의 다음 batch로 갑니다.
2 batch × 3 slot 본문 실행은 전체 6회입니다.
batch 1 · 전체 순서 1–3
- slot 1
(1, 1)· 전체 순서 1 - slot 2
(1, 2)· 전체 순서 2 - slot 3
(1, 3)· 전체 순서 3
batch 2 · 전체 순서 4–6
- slot 1
(2, 1)· 전체 순서 4 - slot 2
(2, 2)· 전체 순서 5 - slot 3
(2, 3)· 전체 순서 6
- 안쪽 continue
- 현재 slot의 나머지만 건너뛰며 같은 batch의 다음 slot은 계속 실행합니다.
- 안쪽 break
- 현재 batch의 안쪽 for만 끝내며 바깥 for는 다음 batch를 시작합니다.
행 header가 바깥 batch를, 열 header가 그 안의 slot을 나타낸다. 제어문 앞의 “안쪽”이 어느 반복을 건너뛰거나 끝내는지 결정한다.
이 예의 조건
- batch 두 회, 각 batch의 slot 세 회입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | 중첩 for의 2×3 실행 순서와 제어 범위 바깥 batch와 안쪽 slot의 2×3 실행 순서 및 continue와 break의 적용 범위 SEMANTIC TABLE · NESTED ITERATION 중첩 for의 2×3 실행 순서와 제어 범위 행은 바깥 batch, 열은 그 안의 slot이다. 두 행과 세 열의 교차점이 정확히 여섯 번 실행되며, 안쪽 continue와 break는 현재 안쪽 반복만 제어한다. 바깥 batch별 안쪽 slot과 전체 실행 순서 바깥 반복 · batch 안쪽 slot 1 안쪽 slot 2 안쪽 slot 3 batch 1 전체 순서 1 · (1, 1) 전체 순서 2 · (1, 2) 전체 순서 3 · (1, 3) batch 2 전체 순서 4 · (2, 1) 전체 순서 5 · (2, 2) 전체 순서 6 · (2, 3) 2 × 3 = 6회 바깥 반복 2회마다 안쪽 반복 3회가 완주하면 본문은 모두 6회 실행됩니다. 안쪽 continue 현재 slot의 남은 본문만 건너뛰고 같은 batch의 다음 slot으로 갑니다. 안쪽 break 현재 batch의 남은 slot을 끝내고 바깥 for의 다음 batch로 갑니다. 2 batch × 3 slot 본문 실행은 전체 6회입니다. batch 1 · 전체 순서 1–3 slot 1 (1, 1) · 전체 순서 1 slot 2 (1, 2) · 전체 순서 2 slot 3 (1, 3) · 전체 순서 3 batch 2 · 전체 순서 4–6 slot 1 (2, 1) · 전체 순서 4 slot 2 (2, 2) · 전체 순서 5 slot 3 (2, 3) · 전체 순서 6 안쪽 continue 현재 slot의 나머지만 건너뛰며 같은 batch의 다음 slot은 계속 실행합니다. 안쪽 break 현재 batch의 안쪽 for만 끝내며 바깥 for는 다음 batch를 시작합니다. 행 header가 바깥 batch를, 열 header가 그 안의 slot을 나타낸다. 제어문 앞의 “안쪽”이 어느 반복을 건너뛰거나 끝내는지 결정한다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 batch batch 1에서 안쪽 slot 반복을 시작합니다. |
|
|
| 다음 batch batch 2에서 안쪽 반복을 새로 실행합니다. |
|
|
| 실행 횟수 두 batch의 본문 실행을 합칩니다. |
|
|
안쪽의 break는 안쪽 반복만 끝낸다
앞 글에서 break와 continue는 가장 가까운 반복문 하나에만 적용된다고 했습니다. 중첩 반복에서는 이 규칙이 결과를 크게 바꿉니다. 다음 프로그램은 3 × 3 격자에서 (2, 2) 칸을 찾으면 break합니다. visited는 본문에 들어간 칸의 수입니다.
public final class PlainBreak {
public static void main(String[] args) {
int visited = 0;
for (int batch = 1; batch <= 3; batch++) {
for (int slot = 1; slot <= 3; slot++) {
visited++;
if (batch == 2 && slot == 2) {
System.out.println("found batch=" + batch + ", slot=" + slot);
break;
}
}
}
System.out.println("visited=" + visited);
}
}found batch=2, slot=2
visited=8(2, 2)를 찾고도 visited가 8인 이유는 break가 안쪽 slot 반복만 끝냈기 때문입니다. batch 1에서 세 칸, batch 2에서 (2, 2)까지 두 칸을 방문한 뒤 안쪽 반복이 끝났고, 바깥 반복은 계속되어 batch 3의 세 칸을 더 방문했습니다. 3 + 2 + 3 = 8입니다. continue도 마찬가지로 안쪽 반복의 현재 회차만 건너뛰고, 같은 batch의 다음 slot으로 넘어갑니다.
찾는 즉시 바깥 반복까지 끝내고 싶다면 라벨(label)을 씁니다. 바깥 for 앞에 search:처럼 이름과 콜론을 붙이고, break search;라고 쓰면 그 이름이 붙은 반복이 통째로 끝납니다.
public final class LabeledBreak {
public static void main(String[] args) {
int visited = 0;
search:
for (int batch = 1; batch <= 3; batch++) {
for (int slot = 1; slot <= 3; slot++) {
visited++;
if (batch == 2 && slot == 2) {
System.out.println("found batch=" + batch + ", slot=" + slot);
break search;
}
}
}
System.out.println("visited=" + visited);
}
}found batch=2, slot=2
visited=5이번에는 (2, 2)에서 두 반복이 함께 끝났으므로 visited는 3 + 2 = 5입니다. 같은 방식으로 continue search;라고 쓰면 안쪽 반복의 남은 회차를 버리고 바깥 반복의 다음 회차로 넘어갑니다.
라벨은 편리하지만, 흐름이 여러 곳으로 뛰어다니는 코드는 읽기 어려워집니다. 그래서 중첩 반복을 메서드로 분리하고, 찾는 즉시 return으로 메서드를 끝내는 방법도 많이 씁니다. 메서드는 메서드와 매개변수에서 배웁니다.
향상된 for로 입력 하나씩 꺼내기
이번에는 앞 글의 일괄 처리 프로그램 BatchWhile을 다시 보겠습니다. 그 프로그램은 index를 직접 관리했고, continue 경로에서도 다음 토큰으로 넘어가도록 index++의 위치를 신경 써야 했습니다. 그런데 이 프로그램이 하는 일은 결국 '실행 인자를 앞에서부터 하나씩 한 번씩 본다'입니다. 이런 반복에는 카운터가 아예 필요 없는 향상된 for(enhanced for)가 더 잘 맞습니다.
for (String token : args)는 'args에 든 문자열을 앞에서부터 하나씩 꺼내 token에 넣고 본문을 실행한다'는 뜻입니다. 콜론 왼쪽에는 꺼낸 값을 담을 변수를, 오른쪽에는 값들이 모여 있는 대상을 씁니다. 실행 인자와 첫 회원가입에서 본 것처럼 args는 문자열 여러 개를 순서대로 담은 배열입니다. 배열을 만들고 인덱스로 다루는 방법은 배열과 인덱스에서 자세히 배우고, 여기서는 '모든 입력을 한 번씩'을 표현하는 방법으로만 씁니다. 나이 부분은 앞 글처럼 정수라고 가정합니다.
public final class BatchFor {
public static void main(String[] args) {
int savedCount = 0;
int totalAge = 0;
for (String token : args) {
if (token.equals("quit")) {
break;
}
int separator = token.indexOf(':');
if (separator <= 0 || separator == token.length() - 1) {
System.out.println("skip=" + token);
continue;
}
String email = token.substring(0, separator);
int age = Integer.parseInt(token.substring(separator + 1));
if (age < 14 || age > 120) {
System.out.println("skip=" + token);
continue;
}
savedCount++;
totalAge += age;
System.out.println("saved=" + email);
}
System.out.println("count=" + savedCount + ", total=" + totalAge);
}
}saved=variables
skip=invalid
saved=conditions
count=2, total=75variables:30과 conditions:45는 검사를 통과해 저장되었고, :가 없는 invalid는 건너뛰었습니다. quit에서 break가 실행되었으므로 그 뒤의 loops:50은 꺼내지 않았습니다. 그래서 합계는 30 + 45 = 75입니다. 향상된 for에서도 break와 continue는 while에서와 똑같이 동작합니다. continue는 다음 원소를 꺼내러 가고, break는 남은 원소를 꺼내지 않고 반복을 끝냅니다.
BatchWhile과 비교하면 index를 선언하고 늘리는 줄이 모두 사라졌습니다. 다음 원소로 넘어가는 일은 향상된 for가 알아서 하므로, continue를 어디에 두든 같은 토큰에 갇힐 걱정이 없습니다. 대신 지금 몇 번째 원소인지는 알 수 없습니다. 화면에 '1번째, 2번째'처럼 번호를 붙이거나 바로 앞 원소와 비교해야 한다면 for (int i = 0; i < args.length; i++)처럼 인덱스를 쓰는 일반 for를 써야 합니다. 이 형태에서 인덱스가 0부터 시작하고 조건이 <=가 아니라 <인 이유는 배열과 인덱스에서 다룹니다.
더 깊이: token에 대입해도 args는 바뀌지 않는다
향상된 for의 변수 token은 원소의 값을 복사해 받는 지역 변수입니다. 본문에서 token = token.toUpperCase();처럼 다른 값을 대입해도 token만 바뀌고 args의 원소는 그대로 남습니다. 원소 자체를 바꾸려면 인덱스를 쓰는 일반 for로 args[i] = ...처럼 대입해야 합니다. 향상된 for는 배열 말고도 Iterable을 구현한 컬렉션에 쓸 수 있는데, 컬렉션을 순회하는 방법은 뒤의 컬렉션 장들에서 다룹니다.
for와 while 중 무엇을 쓸까
두 반복문은 서로 바꿔 쓸 수 있으므로, 어느 쪽이 '가능한가'는 기준이 되지 못합니다. 기준은 반복이 언제 끝나는지가 어디에 가장 분명하게 드러나느냐입니다.
| 반복의 모양 | 먼저 고려할 형태 | 카운터가 살아 있는 범위 |
|---|---|---|
| 정해진 횟수, 정해진 범위 | 일반 for | for 안 |
| 모든 입력을 한 번씩, 번호는 필요 없음 | 향상된 for | 카운터 없음 |
| 종료 명령이 나올 때까지 | while | 카운터가 없거나, 있다면 반복문 바깥(반복이 끝난 뒤에도 남음) |
| 합계가 한계를 넘을 때까지 | while, 또는 조건 칸을 비운 for와 break | while이면 반복문 바깥, for면 for 안 |
| 행과 열의 모든 조합 | 중첩 for | 각 for 안에 따로 |
횟수나 범위를 미리 알면 for가 그 사실을 머리 한 줄에 보여 주고, 카운터도 반복 안에만 머물게 합니다. 반대로 종료 시점이 사용자의 명령처럼 반복 도중에 일어나는 일에 달려 있다면, for의 증감식 칸이 할 일이 없으므로 while이 더 자연스럽습니다. 본문에서 계산한 합계로 멈추더라도 ForWithBreak처럼 카운터가 일정하게 늘어난다면 조건 칸을 비운 for를 쓸 수 있습니다. 변수가 필요한 범위를 좁힐 수 있다는 점도 for를 고르는 이유가 됩니다.
정리
for (초기화; 조건; 증감식)은 초기화를 한 번 실행한 뒤 조건 검사, 본문, 증감식을 되풀이합니다. 증감식은 본문이 끝난 뒤에 실행됩니다.for머리에서 선언한 카운터는 반복 안에서만 쓸 수 있고,continue가 실행되어도 증감식은 실행됩니다.- 조건 칸을 비우면 늘 참으로 취급되므로, 본문에서
break나return을 실행해야 반복이 끝납니다. - 중첩 반복의 본문은 바깥 횟수 × 안쪽 횟수만큼 실행되고, 안쪽의
break와continue는 안쪽 반복에만 적용됩니다. 바깥까지 끝내려면 라벨을 붙인break나 메서드의return을 씁니다. - 모든 원소를 한 번씩 보면 되고 번호가 필요 없다면 향상된
for가 가장 간단합니다.
연습 문제
- 가입 요청 묶음 5개마다 처리 칸 3개를 중첩
for로묶음-칸형식(예:1-1)으로 출력하는SignupSlots를 작성하세요. 단, 세 번째 묶음의 두 번째 칸은 점검 중이라continue로 건너뛰고, 마지막에 출력한 칸의 수를printed=로 표시합니다. - 다음 코드에서
runs++는 몇 번 실행될까요? 안쪽 반복의 시작값이 바깥 카운터i라는 점에 주의하세요.
int runs = 0;
for (int i = 1; i <= 4; i++) {
for (int j = i; j <= 4; j++) {
runs++;
}
}- 아래 코드는 컴파일되지 않습니다. 이유를 설명하고, 반복이 끝난 뒤 마지막 회원 번호를 출력하려면 어떻게 고쳐야 하는지 쓰세요.
for (int memberNumber = 1; memberNumber <= 3; memberNumber++) {
System.out.println(memberNumber);
}
System.out.println("last=" + memberNumber);해설 보기
1번. 점검 중인 칸에서 continue하면 안쪽 반복의 그 회차만 건너뜁니다.
public final class SignupSlots {
public static void main(String[] args) {
int printed = 0;
for (int batch = 1; batch <= 5; batch++) {
for (int slot = 1; slot <= 3; slot++) {
if (batch == 3 && slot == 2) {
continue;
}
printed++;
System.out.println(batch + "-" + slot);
}
}
System.out.println("printed=" + printed);
}
}출력은 1-1부터 5-3까지 열네 줄이고, 그중 세 번째 묶음은 3-1, 3-3 두 줄뿐입니다. 마지막 줄은 printed=14입니다. 전체 조합 5 × 3 = 15개 가운데 하나를 건너뛰었기 때문입니다. continue는 안쪽 slot 반복의 현재 회차만 끝내므로, 같은 묶음의 세 번째 칸과 그 뒤의 묶음은 그대로 실행됩니다. 여기에 continue 대신 break를 쓰면 세 번째 묶음의 세 번째 칸까지 건너뛰어 printed=13이 됩니다.
2번. 10번입니다. i가 1일 때 j는 1부터 4까지 4번, i가 2일 때 2부터 4까지 3번, i가 3일 때 2번, i가 4일 때 1번 돌므로 4 + 3 + 2 + 1 = 10입니다. 안쪽 반복의 횟수가 바깥 회차마다 달라지면 단순히 곱하지 말고 회차별 횟수를 더해야 합니다.
3번. memberNumber는 for 머리에서 선언했으므로 for 안에서만 쓸 수 있습니다. 반복이 끝난 뒤의 줄에서는 이 이름을 찾을 수 없어 cannot find symbol 컴파일 오류가 납니다. 반복이 끝난 뒤에도 값이 필요하다면 변수를 반복 바깥에서 선언합니다.
int lastMember = 0;
for (int memberNumber = 1; memberNumber <= 3; memberNumber++) {
System.out.println(memberNumber);
lastMember = memberNumber;
}
System.out.println("last=" + lastMember);이렇게 하면 1, 2, 3 다음에 last=3이 출력됩니다. 카운터 자체를 바깥에 선언하면 반복이 끝난 뒤의 값은 조건을 거짓으로 만든 4가 되므로, '마지막으로 처리한 번호'가 필요하다면 이처럼 따로 저장하는 편이 의도가 분명합니다.
다음 글
변수 스코프와 숫자 형변환에서는 이 글에서 잠깐 본 '변수를 쓸 수 있는 범위'를 자세히 살펴보고, 숫자를 다른 타입으로 바꿀 때 값이 잘리거나 넘치는 경우를 다룹니다.