제어문과 반복문 — 흐름을 다루는 기본기
프로그래밍에서 "흐름 제어"는 가장 기본적인 도구다. 조건에 따라 다른 코드를 실행하고, 같은 작업을 반복하고, 특정 조건에서 빠져나오고 — 이 세 가지를 할 수 있으면 어떤 로직이든 만들 수 있다. Java의 제어문과 반복문을 한 번에 정리해보자.
조건문 — if/else
기본 구조
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else if (score >= 70) {
System.out.println("C");
} else {
System.out.println("F");
}
// 출력: B
- 위에서 아래로 조건을 검사하고, 처음
true가 되는 블록만 실행한다. else는 선택이다. 모든 조건에 해당하지 않을 때 실행된다.
중괄호를 생략하면?
if (score >= 90)
System.out.println("A");
System.out.println("축하합니다"); // 이건 항상 실행됨!
중괄호 없이 쓰면 바로 다음 한 줄만 if에 속한다. 두 번째 줄은 if와 무관하게 항상 실행된다. 실수하기 딱 좋은 구조이므로, 중괄호를 항상 쓰는 게 좋다.
삼항 연산자
간단한 조건은 삼항 연산자로 한 줄에 쓸 수 있다.
int score = 85;
String grade = (score >= 90) ? "A" : "B 이하";
// grade = "B 이하"
조건 ? 참일 때 값 : 거짓일 때 값 형태다. 간결하지만 중첩하면 가독성이 급격히 떨어지므로 한 단계만 쓰는 게 좋다.
조건문 — switch
여러 값 중 하나를 선택할 때는 switch가 if-else if 체인보다 깔끔할 수 있다.
전통적인 switch
int day = 3;
switch (day) {
case 1:
System.out.println("월요일");
break;
case 2:
System.out.println("화요일");
break;
case 3:
System.out.println("수요일");
break;
default:
System.out.println("기타");
break;
}
// 출력: 수요일
break를 빼먹으면?
switch (day) {
case 1:
System.out.println("월요일");
// break 없음 → 아래로 쭉 실행 (fall-through)
case 2:
System.out.println("화요일");
case 3:
System.out.println("수요일");
}
// day가 1이면: 월요일, 화요일, 수요일 모두 출력!
break를 빼먹으면 다음 case로 쭉 떨어진다. 이걸 fall-through라고 한다. 의도적으로 쓰는 경우도 있지만 대부분은 실수다.
Enhanced switch (Java 14+)
Java 14부터는 화살표(→) 문법으로 fall-through 없는 switch를 쓸 수 있다.
int day = 3;
switch (day) {
case 1 -> System.out.println("월요일");
case 2 -> System.out.println("화요일");
case 3 -> System.out.println("수요일");
case 4 -> System.out.println("목요일");
case 5 -> System.out.println("금요일");
case 6, 7 -> System.out.println("주말");
default -> System.out.println("잘못된 입력");
}
break가 필요 없다 — 화살표 문법은 자동으로 해당 case만 실행- 여러 값을 콤마로 묶을 수 있다 (
case 6, 7)
switch 표현식
값을 반환하는 switch도 쓸 수 있다.
String dayName = switch (day) {
case 1 -> "월요일";
case 2 -> "화요일";
case 3 -> "수요일";
case 4 -> "목요일";
case 5 -> "금요일";
case 6, 7 -> "주말";
default -> "잘못된 입력";
};
System.out.println(dayName); // 수요일
여러 줄이 필요하면 yield를 쓴다.
String description = switch (day) {
case 1, 2, 3, 4, 5 -> {
String name = getDayName(day);
yield name + " (평일)";
}
case 6, 7 -> "주말";
default -> "잘못된 입력";
};
switch에 쓸 수 있는 타입
// Java 7 이전: byte, short, char, int
// Java 7+: String 추가
// Java 14+: 화살표 문법
// Java 21+: 패턴 매칭 (record, sealed class 등)
String command = "start";
switch (command) {
case "start" -> System.out.println("시작");
case "stop" -> System.out.println("정지");
case "pause" -> System.out.println("일시정지");
default -> System.out.println("알 수 없는 명령");
}
반복문 — for
기본 for문
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
// 0, 1, 2, 3, 4
구조: for (초기화; 조건; 증감)
- 초기화: 반복 변수 선언 (
int i = 0) - 조건:
true인 동안 반복 (i < 5) - 증감: 매 반복 후 실행 (
i++)
향상된 for문 (for-each)
배열이나 컬렉션을 순회할 때는 for-each가 깔끔하다.
int[] numbers = {10, 20, 30, 40, 50};
for (int num : numbers) {
System.out.println(num);
}
for (타입 변수 : 배열또는컬렉션) 형태다. 인덱스가 필요 없을 때 쓰면 좋다.
List<String> names = List.of("Alice", "Bob", "Charlie");
for (String name : names) {
System.out.println(name);
}
인덱스가 필요하면?
for-each에서는 인덱스에 접근할 수 없다. 인덱스가 필요하면 기본 for문을 쓴다.
String[] names = {"Alice", "Bob", "Charlie"};
for (int i = 0; i < names.length; i++) {
System.out.println(i + ": " + names[i]);
}
// 0: Alice
// 1: Bob
// 2: Charlie
반복문 — while / do-while
while
조건이 true인 동안 반복한다.
int count = 0;
while (count < 5) {
System.out.println(count);
count++;
}
// 0, 1, 2, 3, 4
반복 횟수가 정해져 있으면 for, 조건 기반이면 while이 자연스럽다.
// 사용자 입력을 "quit"가 나올 때까지 받기
Scanner scanner = new Scanner(System.in);
String input = "";
while (!input.equals("quit")) {
System.out.print("입력: ");
input = scanner.nextLine();
System.out.println(">> " + input);
}
do-while
do-while은 최소 한 번은 실행한다는 점이 while과 다르다.
int count = 10;
// while: 조건 먼저 검사 → 한 번도 실행 안 됨
while (count < 5) {
System.out.println("while: " + count);
count++;
}
// do-while: 일단 한 번 실행 → 그 다음 조건 검사
count = 10;
do {
System.out.println("do-while: " + count);
count++;
} while (count < 5);
// 출력: do-while: 10
실무에서 do-while은 상대적으로 드물게 쓰이지만, "최소 한 번은 실행해야 하는" 로직에서 유용하다.
무한 루프
의도적으로 무한 반복을 만들 때가 있다. 서버가 요청을 계속 대기하거나, 게임 루프 같은 경우다.
// 방법 1: while(true)
while (true) {
// 작업 수행
if (종료조건) break;
}
// 방법 2: for(;;)
for (;;) {
// 작업 수행
if (종료조건) break;
}
둘 다 똑같이 동작하지만, while (true)가 더 흔하고 의도가 명확하다.
break와 continue
break — 반복문 탈출
for (int i = 0; i < 10; i++) {
if (i == 5) break; // i가 5면 반복 중단
System.out.println(i);
}
// 0, 1, 2, 3, 4
continue — 다음 반복으로 건너뛰기
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 짝수면 건너뜀
System.out.println(i);
}
// 1, 3, 5, 7, 9
레이블(Label) — 중첩 반복문 탈출
중첩 반복문에서 바깥 반복문을 탈출하고 싶을 때 레이블을 쓴다.
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outer; // 바깥 for문까지 탈출
}
System.out.println(i + ", " + j);
}
}
// 0, 0
// 0, 1
// 0, 2
// 1, 0
레이블 없이 break만 쓰면 안쪽 for문만 탈출한다. 레이블은 복잡한 중첩 구조에서 유용하지만, 너무 많이 쓰면 코드가 읽기 어려워진다. 가능하면 메서드를 분리하는 게 낫다.
실전 패턴
패턴 1: 배열에서 특정 값 찾기
int[] numbers = {3, 7, 2, 9, 5};
int target = 9;
int index = -1;
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == target) {
index = i;
break; // 찾으면 즉시 탈출
}
}
System.out.println(index); // 3
패턴 2: 중첩 루프로 구구단
for (int i = 2; i <= 9; i++) {
System.out.println("--- " + i + "단 ---");
for (int j = 1; j <= 9; j++) {
System.out.printf("%d x %d = %d%n", i, j, i * j);
}
}
패턴 3: 입력 검증 루프
Scanner scanner = new Scanner(System.in);
int age;
while (true) {
System.out.print("나이를 입력하세요 (1-150): ");
age = scanner.nextInt();
if (age >= 1 && age <= 150) break;
System.out.println("잘못된 입력입니다. 다시 입력해주세요.");
}
System.out.println("입력한 나이: " + age);
패턴 4: 이중 루프에서 조건 필터링
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 5보다 큰 값만 출력
for (int[] row : matrix) {
for (int value : row) {
if (value <= 5) continue;
System.out.println(value);
}
}
// 6, 7, 8, 9
자주 하는 실수
1. 무한 루프에 빠지기
int i = 0;
while (i < 10) {
System.out.println(i);
// i++ 을 빼먹으면 영원히 0만 출력
}
2. off-by-one 에러
int[] arr = {10, 20, 30};
// 잘못된 코드 — arr[3]은 ArrayIndexOutOfBoundsException
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
// 올바른 코드 — < 사용
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
배열 인덱스는 0부터 시작하고, length는 1부터 센 개수다. 그래서 i < arr.length가 맞다 (<=가 아니라).
3. switch에서 break 빠뜨리기
// 화살표 문법을 쓰면 이 실수를 원천 차단할 수 있다
switch (value) {
case 1 -> doFirst();
case 2 -> doSecond();
default -> doDefault();
}
▸ TIP 이 글의 코드 예제를 직접 실행해보고 싶다면 Java 기본기 핸드북을 확인해보세요.
정리
- if/else: 조건 분기의 기본. 중괄호를 항상 쓰자
- switch: 여러 값 비교 시 유용. Java 14+에서는 화살표 문법으로 fall-through 방지
- for: 정해진 횟수 반복. 배열/컬렉션 순회 시 for-each 활용
- while: 조건 기반 반복.
do-while은 최소 한 번 실행 보장 - break/continue: 반복 흐름 제어. 레이블은 중첩 탈출 시에만
- Enhanced switch: 가능하면 화살표 문법을 쓰자 — break 실수를 방지하고 코드도 깔끔하다
다음 글에서는 메서드와 배열을 다룬다. 코드를 나누는 방법(메서드)과 데이터를 묶는 방법(배열)은 어떤 프로그램이든 기본이 되는 주제다.