4.2 반복문 (Loops)
반복문은 어떤 작업이 반복적으로 수행되도록 할 때 사용됩니다. 주어진 조건이 true인 동안 블록 안의 문장들을 반복해서 실행합니다. Java에는 for, while, do-while, 향상된 for 네 가지 반복문이 있습니다.
1. while 문
while문은 반복 횟수를 미리 알 수 없고, 조건에 따라 반복 여부를 결정할 때 주로 사용합니다. 조건식이 true인 한 블록 안의 코드를 계속 반복합니다.
기본 구조
// 구조: while (조건식) { 반복할 코드 }
int i = 1;
while (i <= 5) {
System.out.println(i + "번째 반복");
i++; // 증감식을 블록 내부에 직접 작성해야 합니다
}
// 출력: 1번째 반복 ~ 5번째 반복
while 문 활용 - 조건 기반 반복
// 주어진 수를 2로 나눠가며 1이 될 때까지의 횟수 세기
int n = 64;
int count = 0;
while (n != 1) {
n /= 2;
count++;
}
System.out.println("2로 " + count + "번 나누면 1이 됩니다."); // 6번
주의점: 무한 루프
조건식이 항상 true이면 영원히 종료되지 않는 무한 루프에 빠집니다. 증감식을 빠뜨리지 않도록 주의하세요.
int i = 1;
while (i <= 5) {
System.out.println(i);
// i++; ← 이 줄을 빠뜨리면 i는 항상 1이므로 무한 루프!
}
의도적인 무한 루프
무한 루프를 의도적으로 사용하는 경우도 있습니다. 반드시 탈출 조건(break)을 함께 작성해야 합니다.
import java.util.Scanner;
public class InfiniteLoopExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int total = 0;
while (true) { // 의도적 무한 루프
System.out.print("숫자를 입력하세요 (0 입력 시 종료): ");
int num = scanner.nextInt();
if (num == 0) {
break; // 0을 입력하면 탈출
}
total += num;
System.out.println("현재 합계: " + total);
}
System.out.println("최종 합계: " + total);
scanner.close();
}
}
2. do-while 문
while문의 변형으로, 블록을 먼저 1회 실행한 후에 조건식을 평가 합니다. 조건이 처음부터 false이더라도 최소 한 번은 반드시 실행됩니다.
기본 구조
// 구조: do { 반복할 코드 } while (조건식);
int i = 10;
do {
System.out.println("실행됨: i = " + i); // 조건이 false여도 1번 실행
i++;
} while (i < 5); // 10 < 5 는 false이지만 이미 1번 실행된 후
// 출력: 실행됨: i = 10
do-while 활용 - 메뉴 선택 패턴
사용자 입력을 받을 때 최소 한 번은 메뉴를 보여줘야 하므로 do-while이 자연스럽습니다.
import java.util.Scanner;
public class MenuExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int choice;
do {
System.out.println("\n===== 메뉴 =====");
System.out.println("1. 새 파일 만들기");
System.out.println("2. 파일 열기");
System.out.println("3. 저장");
System.out.println("0. 종료");
System.out.print("선택: ");
choice = scanner.nextInt();
switch (choice) {
case 1: System.out.println("새 파일을 만듭니다."); break;
case 2: System.out.println("파일을 엽니다."); break;
case 3: System.out.println("파일을 저장합니다."); break;
case 0: System.out.println("종료합니다."); break;
default: System.out.println("잘못된 선택입니다.");
}
} while (choice != 0); // 0을 선택할 때까지 반복
scanner.close();
}
}
3. for 문
for문은 반복 횟수를 이미 알고 있을 때 주로 사용합니다. 초기화, 조건식, 증감식을 한 줄에서 확인할 수 있어 가독성이 좋습니다.
기본 구조
// 구조: for (초기화; 조건식; 증감식) { 반복할 코드 }
for (int i = 1; i <= 5; i++) {
System.out.println("Hello Java! " + i);
}
// 출력: Hello Java! 1 ~ Hello Java! 5
초기화, 조건식, 증감식 변형
// 역방향 반복
for (int i = 10; i >= 1; i--) {
System.out.print(i + " "); // 10 9 8 7 6 5 4 3 2 1
}
// 2씩 증가
for (int i = 0; i <= 20; i += 2) {
System.out.print(i + " "); // 0 2 4 6 8 10 12 14 16 18 20
}
// 초기화와 증감식에 쉼표로 여러 변수 사용
for (int i = 0, j = 10; i < j; i++, j--) {
System.out.println("i=" + i + ", j=" + j);
}
1부터 N까지 합산
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i; // 각 반복마다 i의 값을 누적
}
System.out.println("1부터 100까지의 합: " + sum); // 5050
반복문에서의 변수 스코프
for문의 초기화에서 선언된 변수는 for문 블록 안에서만 유효합니다.
for (int i = 0; i < 3; i++) {
int temp = i * 2; // temp는 각 반복마다 새로 생성됨
System.out.println(temp);
}
// System.out.println(i); // 컴파일 오류: i는 for문 밖에서 사용 불가
// System.out.println(temp); // 컴파일 오류: temp도 사용 불가
4. 향상된 for 문 (Enhanced for loop / for-each)
배열이나 컬렉션의 모든 요소를 읽기 전용으로 순회 할 때 코드를 매우 간결하게 작성할 수 있습니다. (JDK 1.5 추가)
기본 사용법
// 구조: for (타입 변수명 : 배열/컬렉션) { 반복할 코드 }
int[] numbers = {10, 20, 30, 40, 50};
// 일반 for 문
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// 향상된 for 문 (동일한 결과, 더 간결)
for (int num : numbers) {
System.out.println(num);
}
컬렉션과 함께 사용
import java.util.List;
List<String> fruits = List.of("사과", "바나나", "오렌지", "포도");
for (String fruit : fruits) {
System.out.println("과일: " + fruit);
}
향상된 for 문으로는 배열의 인덱스를 알 수 없고, 배열 요소를 수정할 수 없습니다. 인덱스가 필요하거나 요소를 수정해야 한다면 일반 for 문을 사용하세요.
int[] numbers = {10, 20, 30};
// 향상된 for 문으로 수정 시도 - 원본 배열이 변경되지 않음!
for (int num : numbers) {
num = num * 2; // num은 복사본이라 원본에 영향 없음
}
System.out.println(numbers[0]); // 여전히 10
// 배열 수정하려면 일반 for 문 사용
for (int i = 0; i < numbers.length; i++) {
numbers[i] = numbers[i] * 2; // 원본 수정 가능
}
System.out.println(numbers[0]); // 20
5. 중첩 반복문
반복문 안에 반복문을 작성할 수 있습니다. 바깥쪽 반복문이 1번 실행될 때 안쪽 반복문은 전체를 실행합니다.
구구단 출력
public class MultiplicationTable {
public static void main(String[] args) {
for (int dan = 2; dan <= 9; dan++) {
System.out.println("=== " + dan + "단 ===");
for (int i = 1; i <= 9; i++) {
System.out.printf("%d x %d = %2d%n", dan, i, dan * i);
}
}
}
}
출력 (일부):
=== 2단 ===
2 x 1 = 2
2 x 2 = 4
...
반복문 성능 팁
// 나쁜 예: 조건식에서 매번 메서드 호출
String[] arr = {"a", "b", "c", "d", "e"};
for (int i = 0; i < arr.length; i++) { // arr.length는 매번 계산
System.out.println(arr[i]);
}
// 좋은 예: 길이를 미리 변수에 저장
int len = arr.length;
for (int i = 0; i < len; i++) { // len 변수를 사용
System.out.println(arr[i]);
}
현대 JVM은 arr.length 호출을 최적화합니다. 하지만 list.size()처럼 계산 비용이 있는 메서드라면 변수에 저장하는 것이 좋습니다.
6. 실전 예제 1: 별(★) 피라미드 패턴
중첩 반복문의 대표적인 연습 문제입니다.
public class StarPatterns {
public static void main(String[] args) {
int n = 5;
// 패턴 1: 직각삼각형
System.out.println("직각삼각형:");
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
System.out.print("*");
}
System.out.println();
}
// 패턴 2: 역삼각형
System.out.println("\n역삼각형:");
for (int i = n; i >= 1; i--) {
for (int j = 0; j < i; j++) {
System.out.print("*");
}
System.out.println();
}
// 패턴 3: 정삼각형 (피라미드)
System.out.println("\n정삼각형(피라미드):");
for (int i = 1; i <= n; i++) {
// 공백 출력
for (int j = 0; j < n - i; j++) {
System.out.print(" ");
}
// 별 출력
for (int j = 0; j < 2 * i - 1; j++) {
System.out.print("*");
}
System.out.println();
}
// 패턴 4: 다이아몬드
System.out.println("\n다이아몬드:");
// 위쪽 절반 (n행)
for (int i = 1; i <= n; i++) {
for (int j = 0; j < n - i; j++) System.out.print(" ");
for (int j = 0; j < 2 * i - 1; j++) System.out.print("*");
System.out.println();
}
// 아래쪽 절반 (n-1행)
for (int i = n - 1; i >= 1; i--) {
for (int j = 0; j < n - i; j++) System.out.print(" ");
for (int j = 0; j < 2 * i - 1; j++) System.out.print("*");
System.out.println();
}
}
}
출력:
직각삼각형:
*
**
***
****
*****
정삼각형(피라미드):
*
***
*****
*******
*********
다이아몬드:
*
***
*****
*******
*********
*******
*****
***
*
7. 실전 예제 2: 소수(Prime Number) 판별
중첩 반복문과 break를 활용한 소수 판별 프로그램입니다.
public class PrimeNumbers {
public static void main(String[] args) {
int limit = 50;
int count = 0;
System.out.println(limit + " 이하의 소수:");
for (int n = 2; n <= limit; n++) {
boolean isPrime = true;
// 2부터 n-1까지 나누어 떨어지는지 확인
for (int i = 2; i < n; i++) {
if (n % i == 0) {
isPrime = false;
break; // 약수를 하나 찾으면 더 검사할 필요 없음
}
}
if (isPrime) {
System.out.print(n + " ");
count++;
}
}
System.out.println("\n총 " + count + "개의 소수");
}
}
출력:
50 이하의 소수:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
총 15개의 소수
위 코드는 i < n까지 검사하지만, 사실 i <= Math.sqrt(n)까지만 검사해도 충분합니다. n의 약수는 반드시 √n 이하에 하나가 존재하기 때문입니다. 이 최적화로 검사 횟수를 크게 줄일 수 있습니다.
for (int i = 2; i <= Math.sqrt(n); i++) { // 최적화된 버전
if (n % i == 0) {
isPrime = false;
break;
}
}
8. 반복문 선택 가이드
| 상황 | 권장 반복문 |
|---|---|
| 반복 횟수를 알고 있을 때 | for |
| 조건이 충족되는 동안 반복 | while |
| 최소 1번 실행 보장 필요 | do-while |
| 배열/컬렉션 전체 순회 (읽기) | 향상된 for |
| 인덱스가 필요한 순회 | for |
| 복잡한 탈출 조건 | while(true) + break |