안동민 개발노트

본문 시작

while·break·continue

반복 상태가 종료 방향으로 움직이는지 추적하고 break와 continue로 회원가입 배치 입력을 제어합니다.

반복 횟수가 아니라 “아직 처리할 입력이 있는가”, “종료 명령을 만났는가”가 계속 여부를 정하면 while이 자연스럽습니다.

반복문은 조건만으로 끝나지 않습니다.

본문이 조건에 사용한 상태를 언젠가 거짓이 되는 방향으로 바꾸는지 함께 확인해야 합니다.


무한 반복 재현

끝없는 프로그램을 그대로 두면 실습 터미널을 멈춰야 하므로 다음 코드는 네 번째 회차에 예외를 던지는 감시 장치를 넣었습니다.

핵심 결함인 count 미변경은 그대로입니다.

lab/NonProgressingWhile.java
public final class NonProgressingWhile {
    public static void main(String[] args) {
        int count = 0;
        int watchdog = 0;

        while (count < 3) {
            System.out.println("count=" + count);
            watchdog++;
            if (watchdog == 4) {
                throw new IllegalStateException("count did not progress");
            }
        }
    }
}
count=0
count=0
count=0
count=0
Exception in thread "main" java.lang.IllegalStateException: count did not progress

조건 count < 3을 다시 검사할 때마다 count는 여전히 0입니다.

watchdog는 오류를 재현하기 위한 안전장치일 뿐 해결책이 아닙니다.

정상 반복은 본래 상태인 count를 증가시킵니다.

src/WhileProgress.java
public final class WhileProgress {
    public static void main(String[] args) {
        int count = 0;

        while (count < 3) {
            System.out.println("count=" + count);
            count++;
        }
        System.out.println("done=" + count);
    }
}
count=0
count=1
count=2
done=3

초기 상태 0, 계속 조건 < 3, 본문의 변화 count++를 한 세트로 읽습니다.

하나가 빠지면 시작하지 않거나, 너무 많이 실행하거나, 끝나지 않습니다.


do-while의 최초 실행

while은 본문 전에 조건을 검사합니다.

시작부터 거짓이면 0회 실행합니다.

do-while은 본문 뒤에서 조건을 검사해 최소 한 번 실행합니다.

src/PreAndPostCheck.java
public final class PreAndPostCheck {
    public static void main(String[] args) {
        int remaining = 0;

        while (remaining > 0) {
            System.out.println("while=" + remaining);
        }

        do {
            System.out.println("do-while=" + remaining);
        } while (remaining > 0);
    }
}
do-while=0

메뉴를 최소 한 번 보여 준 뒤 계속 여부를 묻는 구조에는 do-while이 맞을 수 있습니다.

반대로 입력이 없으면 아무것도 처리하지 않아야 하는 배치 작업에 쓰면 가짜 한 회가 생깁니다.

“최소 한 번 실행”이 실제 요구인지 먼저 확인합니다.

while과 do-while의 첫 검사 위치 및 상태 진전에 따른 종료 흐름

FLOWCHART · LOOP PROGRESS

while 종료성은 검사 위치와 상태 진전이 결정한다

while은 조건에서 시작하고 do-while은 본문에서 시작하지만, 두 반복 모두 본문이 상태를 종료 방향으로 움직인 뒤 조건으로 돌아와야 끝납니다.

while 종료성은 검사 위치와 상태 진전이 결정한다 while은 조건을 먼저 검사하고 do-while은 본문을 먼저 실행하지만, 공통 본문 뒤 count가 0에서 3으로 진전되어야 조건을 빠져나오며 진전이 없으면 0을 반복해 종료되지 않는 흐름입니다. 조건 먼저 거짓 참 본문 먼저 진전 누락 조건 복귀 while 진입 · count=0 본문보다 조건을 먼저 검사 count < 3 ? 반복 조건 재검사 반복 종료 첫 false면 본문 0회 진전 뒤 false면 done=3 do-while 진입 조건 전에 본문 최소 1회 본문 실행 현재 count를 출력 BODY · THEN PROGRESS 진전이 없는 실패 trace 0→0→… · 같은 상태 반복 watchdog 없이는 종료되지 않음 count++ · 상태 진전 0→1→2→3→done ENTRY DIFFERS · PROGRESS AND RECHECK CLOSE BOTH LOOPS

반복 종료 계약 첫 검사 위치와 매 회 상태 진전을 분리해 봅니다.

  1. while은 조건부터

    while은 본문 전에 조건을 검사하므로 처음부터 false면 본문을 0회 실행합니다.

  2. do-while은 본문부터

    do-while은 본문 뒤에서 조건을 검사하므로 처음 조건이 false여도 본문을 최소 1회 실행합니다.

  3. 종료 가능한 전체 계약

    초기 상태, 조건, 본문, 상태 진전, 조건 복귀가 한 세트입니다. 본문은 조건에 쓰인 상태를 종료 방향으로 움직여야 합니다.

  4. 성공 trace

    count++가 매 회 실행되면 0→1→2→3→done으로 진전되어 count < 3을 빠져나갑니다.

  5. 진전 누락 trace

    count++가 없으면 0→0→…으로 같은 상태를 반복하며 watchdog 같은 외부 안전장치 없이는 종료되지 않습니다.

  • 조건·본문·조건 복귀
  • 상태 진전이 누락된 미종료 trace
  • 종료 방향의 상태 진전

while과 do-while의 차이는 첫 검사 위치이고, 두 반복의 공통 종료 조건은 모든 반복 경로에서 상태가 실제로 진전되는 것입니다.


break와 continue

src/LoopControl.java
public final class LoopControl {
    public static void main(String[] args) {
        int value = 0;

        while (value < 8) {
            value++;

            if (value % 2 == 0) {
                continue;
            }
            if (value > 5) {
                break;
            }
            System.out.println(value);
        }

        System.out.println("stoppedAt=" + value);
    }
}
1
3
5
stoppedAt=7

짝수에서는 continue가 아래 문장을 건너뛰고 조건 검사로 돌아갑니다.

7에서는 break가 while 자체를 끝냅니다.

value++를 continue 아래에 두면 짝수에서 증가가 건너뛰어 같은 값에 갇힐 수 있습니다.

반복 상태 갱신은 continue보다 먼저 실행되거나 모든 경로에서 실행됨이 보장돼야 합니다.

break와 continue가 중첩 반복 안에 있으면 기본적으로 가장 가까운 반복문 하나에만 적용됩니다.

바깥 반복까지 제어할 필요가 있다면 메서드로 추출하거나 라벨을 쓸 수 있지만 먼저 구조를 단순화할 방법을 찾습니다.


while 기반 일괄 입력

batch 뒤에는 email:age 토큰이 여러 개 오며 quit을 만나면 남은 입력을 처리하지 않습니다.

이 절에서는 나이 부분이 정수라는 입력 전제를 두고 반복 제어에 집중합니다.

숫자가 아닌 문자열까지 복구하는 try/catch는 ch3-2에서 처음 정의합니다.

src/BatchWhile.java
public final class BatchWhile {
    public static void main(String[] args) {
        int index = 0;
        int savedCount = 0;
        int totalAge = 0;

        while (index < args.length) {
            String token = args[index];
            index++;

            if (token.equals("quit")) {
                break;
            }

            int separator = token.indexOf(':');
            if (separator <= 0 || separator == token.length() - 1) {
                System.out.println("skip.format=" + 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.range=" + token);
                continue;
            }

            savedCount++;
            totalAge += age;
            System.out.println("saved=" + email + ":" + age);
        }

        System.out.println("count=" + savedCount);
        System.out.println("total=" + totalAge);
    }
}
java -cp out BatchWhile java:30 bad sql:0 loops:45 quit ignored:50
saved=java:30
skip.format=bad
skip.range=sql:0
saved=loops:45
count=2
total=75

index++를 토큰을 읽은 직후에 두었기 때문에 어느 continue 경로에서도 다음 입력으로 이동합니다.

quit은 break로 반복을 끝내므로 ignored:50은 읽지 않습니다.

저장된 항목만 count와 total을 바꿉니다.


반복 불변식

배치 반복의 각 조건 검사 직전에는 다음 사실이 유지돼야 합니다.

  • index는 이미 확인한 토큰 수이며 0부터 args.length 사이입니다.
  • savedCount는 유효해 저장한 항목 수입니다.
  • totalAge는 저장한 회원의 나이만 더한 값입니다.
  • 현재 토큰이 실패해도 index는 다음 토큰을 가리킵니다.

이처럼 매 회차의 시작과 끝에 유지되는 조건을 반복 불변식이라고 합니다.

출력이 틀렸다면 마지막으로 불변식이 맞았던 회차와 처음 깨진 회차 사이를 확인합니다.

배치 while의 loop-head 불변식과 continue, save, break 제어 흐름

FLOWCHART · LOOP INVARIANT

배치 while의 진행·continue·break 상태 계약

token을 고른 즉시 index를 증가시켜 모든 경로의 진행을 보장하고, 실패는 누적값을 보존한 채 continue하며 quit만 남은 입력을 미도달로 남깁니다.

배치 while의 진행·continue·break 상태 계약 loop head의 index와 누적값 불변식에서 token을 고른 직후 index를 증가시키고, quit은 break, format 또는 range 실패는 aggregate를 보존한 continue, 유효 token은 count와 total을 갱신한 뒤 다음 loop head로 돌아가는 흐름입니다. 아니요 예 예 · break 아니요 아니요 예 아니요 예 LOOP HEAD INVARIANT index < args.length? 0 ≤ index ≤ args.length count·total = 유효 저장 누적 입력 소진 index == args.length 최종 count · total 출력 token = args[index] 즉시 index++ 모든 제어 이동보다 먼저 진전 quit 명령인가? 종료 명령 판정 break · 즉시 종료 quit 뒤 token은 NOT REACHED ignored:50 · 미처리 형식이 유효한가? 구분자 존재 · 양끝 제외 14 ≤ age ≤ 120 ? parseInt · 정수 전제 continue · aggregate 보존 format·range 실패 · count/total 불변 NEXT LOOP HEAD 유효 token 저장 count++ · total += age 함께 갱신 후 loop head INTEGER INPUT ASSUMPTION · NUMBER PARSE FAILURE RECOVERY → ch3-2

loop-head invariant 0 ≤ index ≤ args.length이며 count와 total은 유효하게 저장한 항목만 누적합니다.

  1. 남은 token 확인

    index < args.length가 false면 입력을 모두 확인한 것이므로 최종 count와 total을 출력합니다.

  2. 선택 즉시 위치 진전

    token = args[index] 직후 index++를 실행합니다. 따라서 continue와 break를 포함한 모든 처리 경로에서 입력 위치가 진전됩니다.

  3. quit은 break

    quit이면 반복을 즉시 끝내며 뒤의 ignored:50 같은 남은 token은 NOT REACHED, 즉 미도달 상태가 됩니다.

  4. format·range 실패는 continue

    separator 형식이 잘못됐거나 나이가 14–120 밖이면 count와 total을 바꾸지 않고 다음 loop head로 돌아갑니다.

  5. 정수 입력 전제

    이 절에서는 나이 문자열이 정수라고 가정합니다. 숫자 변환 실패 복구는 ch3-2에서 다루므로 현재 continue 경로가 아닙니다.

  6. 유효 token만 저장

    검사를 통과한 token만 count++와 total += age를 함께 실행한 뒤 다음 loop head로 돌아갑니다.

  • 조건·선택·판정 흐름
  • aggregate를 보존하는 continue
  • 유효 token의 count·total 갱신

배치 반복의 안전성은 continue 위치가 아니라 그 전에 index가 진전됐는지, 그리고 실패 경로가 저장 누적값을 바꾸지 않았는지로 판단합니다.


while과 do-while 선택

요구적합한 반복
입력이 있는 동안만 처리while
메뉴를 먼저 한 번 표시do-while 가능
종료 명령까지 계속while (true) + break 가능
잘못된 항목만 건너뜀continue
전체 배치를 즉시 중단break

while (true)는 종료 경로가 본문 여러 곳에 있을 때 쓸 수 있지만 모든 break 조건을 찾아야 전체 종료성을 판단할 수 있습니다.

조건에 핵심 상태를 직접 쓸 수 있다면 while (index < args.length)처럼 표현하는 편이 낫습니다.


연습 문제

1부터 시작해 홀수만 합계에 더합니다.

합계가 30을 넘으면 break, 짝수는 continue로 건너뜁니다.

매 회차에서 숫자가 반드시 증가해야 하며 마지막 합계와 종료 숫자를 출력하세요.

해설 보기
src/OddSumLimit.java
public final class OddSumLimit {
    public static void main(String[] args) {
        int number = 0;
        int sum = 0;

        while (true) {
            number++;
            if (number % 2 == 0) {
                continue;
            }

            sum += number;
            if (sum > 30) {
                break;
            }
        }

        System.out.println("number=" + number);
        System.out.println("sum=" + sum);
    }
}
number=11
sum=36

number++가 continue보다 앞에 있어 짝수에서도 진행합니다.

한계 검사는 홀수를 더한 뒤에 있으므로 “30을 넘긴 첫 합계”가 결과입니다.

30 이상이 되기 전에 멈추려면 더하기 전에 다음 합계를 계산해 비교해야 하며 요구가 달라집니다.