본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
2장 : 값·식·분기·반복

while·break·continue

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

반복 횟수가 아니라 “아직 처리할 입력이 있는가”, “종료 명령을 만났는가”가 계속 여부를 정하면 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이 맞을 수 있습니다. 반대로 입력이 없으면 아무것도 처리하지 않아야 하는 배치 작업에 쓰면 가짜 한 회가 생깁니다. “최소 한 번 실행”이 실제 요구인지 먼저 확인합니다.

breakcontinue

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에서는 breakwhile 자체를 끝냅니다. value++continue 아래에 두면 짝수에서 증가가 건너뛰어 같은 값에 갇힐 수 있습니다. 반복 상태 갱신은 continue보다 먼저 실행되거나 모든 경로에서 실행됨이 보장돼야 합니다.

breakcontinue가 중첩 반복 안에 있으면 기본적으로 가장 가까운 반복문 하나에만 적용됩니다. 바깥 반복까지 제어할 필요가 있다면 메서드로 추출하거나 라벨을 쓸 수 있지만 먼저 구조를 단순화할 방법을 찾습니다.

while 기반 일괄 입력

batch 뒤에는 topic:minutes 토큰이 여러 개 오며 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 totalMinutes = 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 topic = token.substring(0, separator);
            int minutes = Integer.parseInt(token.substring(separator + 1));

            if (minutes < 1 || minutes > 600) {
                System.out.println("skip.range=" + token);
                continue;
            }

            savedCount++;
            totalMinutes += minutes;
            System.out.println("saved=" + topic + ":" + minutes);
        }

        System.out.println("count=" + savedCount);
        System.out.println("total=" + totalMinutes);
    }
}
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 경로에서도 다음 입력으로 이동합니다. quitbreak로 반복을 끝내므로 ignored:50은 읽지 않습니다. 저장된 항목만 counttotal을 바꿉니다.

반복 불변식

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

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

이처럼 매 회차의 시작과 끝에 유지되는 조건을 반복 불변식이라고 합니다. 출력이 틀렸다면 마지막으로 불변식이 맞았던 회차와 처음 깨진 회차 사이를 확인합니다.

whiledo-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 이상이 되기 전에 멈추려면 더하기 전에 다음 합계를 계산해 비교해야 하며 요구가 달라집니다.