제어문과 반복문 — 흐름을 다루는 기본기
조건에 따라 다른 코드를 실행하고, 같은 작업을 반복하고, 특정 조건에서 빠져나오기 — 이 세 가지만 할 수 있으면 어떤 로직이든 만들 수 있다. Java의 제어문과 반복문을 한 번에 정리해보자.
조건문 — if/else
기본 구조
if는 조건을 위에서 아래로 검사하고, 처음 true가 되는 블록만 실행해요.
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else {
System.out.println("F");
}
// 출력: B
else는 선택입니다. 모든 조건에 해당하지 않을 때의 기본 동작을 정의해요.
중괄호를 생략하면 생기는 문제
if (score >= 90)
System.out.println("A");
System.out.println("축하합니다"); // 이건 항상 실행됨!
중괄호 없이 쓰면 바로 다음 한 줄만 if에 속합니다. 들여쓰기가 같아도 두 번째 줄은 if와 무관하게 항상 실행돼요. 이런 실수를 방지하려면 ** 중괄호를 항상 쓰는 게 좋습니다.**
삼항 연산자
간단한 조건은 한 줄로 쓸 수 있어요.
String grade = (score >= 90) ? "A" : "B 이하";
조건 ? 참일 때 값 : 거짓일 때 값 형태입니다. 중첩하면 가독성이 급격히 떨어지므로 한 단계만 쓰는 게 좋아요.
조건문 — switch
여러 값 중 하나를 선택할 때는 switch가 if-else if 체인보다 깔끔할 수 있습니다.
전통적인 switch와 fall-through 문제
전통 switch에서는 각 case 끝에 break를 써야 합니다. break를 빼먹으면 다음 case로 쭉 떨어지는데, 이걸 fall-through 라고 해요.
switch (day) {
case 1:
System.out.println("월요일");
// break 없음 → 아래로 쭉 실행
case 2:
System.out.println("화요일");
case 3:
System.out.println("수요일");
}
// day가 1이면: 월요일, 화요일, 수요일 모두 출력!
의도적으로 쓰는 경우도 있지만, 대부분은 실수입니다. Java 14에서 이 문제를 근본적으로 해결한 문법이 나왔어요.
Enhanced switch (Java 14+)
화살표(→) 문법은 fall-through가 원천적으로 불가능합니다. 해당 case만 실행되고, break도 필요 없어요.
switch (day) {
case 1 -> System.out.println("월요일");
case 2 -> System.out.println("화요일");
case 3 -> System.out.println("수요일");
case 6, 7 -> System.out.println("주말");
default -> System.out.println("기타");
}
여러 값을 콤마로 묶을 수도 있어요 (case 6, 7). 전통 switch에서는 이렇게 하려면 fall-through를 의도적으로 써야 했는데, 화살표 문법에서는 깔끔하게 표현됩니다.
switch 표현식 — 값을 반환하는 switch
switch를 표현식 으로 쓰면 값을 직접 반환할 수 있습니다.
String dayName = switch (day) {
case 1 -> "월요일";
case 2 -> "화요일";
case 3 -> "수요일";
case 6, 7 -> "주말";
default -> "기타";
};
여러 줄이 필요하면 블록으로 감싸고 yield를 씁니다.
String description = switch (day) {
case 1, 2, 3, 4, 5 -> {
String name = getDayName(day);
yield name + " (평일)";
}
case 6, 7 -> "주말";
default -> "잘못된 입력";
};
반복문 — for
기본 for문
for (초기화; 조건; 증감) 구조로, 반복 횟수가 정해져 있을 때 씁니다.
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
// 0, 1, 2, 3, 4
- **초기화 **: 반복 변수 선언 (
int i = 0) - ** 조건 **:
true인 동안 반복 (i < 5) - ** 증감 **: 매 반복이 끝난 후 실행 (
i++)
for-each — 인덱스가 필요 없을 때
배열이나 컬렉션을 순회할 때는 for-each가 간결해요.
int[] numbers = {10, 20, 30, 40, 50};
for (int num : numbers) {
System.out.println(num);
}
인덱스가 필요하면 기본 for문을 쓰면 됩니다. for-each에서는 인덱스에 접근할 수 없기 때문이에요.
for (int i = 0; i < names.length; i++) {
System.out.println(i + ": " + names[i]);
}
반복문 — while / do-while
while
조건이 true인 동안 반복합니다. 반복 횟수가 정해져 있으면 for, 조건 기반이면 while이 자연스러워요.
int count = 0;
while (count < 5) {
System.out.println(count);
count++;
}
do-while — 최소 한 번 실행 보장
do-while은 조건을 ** 나중에** 검사하기 때문에 최소 한 번은 실행됩니다.
int count = 10;
do {
System.out.println("do-while: " + count);
count++;
} while (count < 5);
// 출력: do-while: 10 (조건이 false지만 한 번은 실행됨)
while이었다면 한 번도 실행되지 않았을 코드예요. "입력 검증" 같이 최소 한 번은 사용자 입력을 받아야 하는 경우에 유용합니다.
break와 continue
break — 반복문 탈출
for (int i = 0; i < 10; i++) {
if (i == 5) break;
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) — 중첩 반복문 탈출
break는 가장 안쪽 반복문만 탈출합니다. 중첩 반복문에서 바깥까지 한 번에 빠져나오려면 레이블을 써야 해요.
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 에서 멈춤
레이블은 중첩 탈출에 유용하지만, 너무 많이 쓰면 코드가 읽기 어려워집니다. 가능하면 메서드를 분리하는 게 더 깔끔해요.
주의할 점
1. 무한 루프에 빠지기
int i = 0;
while (i < 10) {
System.out.println(i);
// i++를 빼먹으면 영원히 0만 출력
}
증감 연산을 빠뜨려서 무한 루프에 빠지는 건 가장 흔한 실수입니다. while을 쓸 때는 ** 루프 탈출 조건이 반드시 갱신되는지** 확인해야 해요.
2. off-by-one 에러
int[] arr = {10, 20, 30};
// 잘못된 코드 — arr[3]은 존재하지 않음
for (int i = 0; i <= arr.length; i++) { // <= 가 문제!
System.out.println(arr[i]); // ArrayIndexOutOfBoundsException
}
배열 인덱스는 0부터 시작하고 length는 개수이므로 마지막 인덱스는 length - 1이에요. i <= arr.length가 아니라 i < arr.length가 맞습니다. 경계값 처리 실수는 반복문에서 가장 자주 발생하는 버그 유형이에요.
3. switch에서 break 빠뜨리기
전통 switch에서 break를 빠뜨리면 fall-through가 발생합니다. 이 버그는 컴파일 에러가 나지 않기 때문에 찾기 어려워요. ** 가능하면 화살표 문법을 쓰는 것 **이 이 실수를 원천 차단하는 방법입니다.
// 화살표 문법 — fall-through 불가능
switch (value) {
case 1 -> doFirst();
case 2 -> doSecond();
default -> doDefault();
}
▸ TIP 이 글의 코드 예제를 직접 실행해보고 싶다면 Java 기본기 핸드북을 확인해보세요.
정리
| 항목 | 설명 |
|---|---|
| if/else | 조건 분기의 기본. 중괄호를 항상 쓰자 |
| switch (전통) | 여러 값 비교. break 빠뜨리면 fall-through 발생 |
| switch (화살표) | Java 14+. fall-through 없음, 값 반환 가능 |
| for | 정해진 횟수 반복. 배열/컬렉션 순회 시 for-each 활용 |
| while / do-while | 조건 기반 반복. do-while은 최소 한 번 실행 보장 |
| break / continue | break는 탈출, continue는 건너뛰기 |
| 레이블 | 중첩 반복문 바깥 탈출 시 사용. 남용하지 말 것 |
다음 글에서는 메서드와 배열을 다뤄볼게요. 코드를 나누는 방법(메서드)과 데이터를 묶는 방법(배열)은 어떤 프로그램이든 기본이 되는 주제입니다.