[Java] Java 02 Control Statements

- Period 01 — 분기를 시작하다
- Period 02 — Switch
- Period 03 — for 반복문
- Period 04 — while / do-while
- Period 05 — 중첩 반복 \& break/continue
- Period 06 — 종합 실습 (미니 계산기)
- Period 07 — 디버거로 흐름 추적
Period 01 — 분기를 시작하다
제어문이 필요한가?
- 정방향 진행의 한계:
- 기본적으로 프로그램은 작성된 순서대로 위에서 아래로, 왼쪽에서 오른쪽으로만 순차적으로 실행되는 특징을 가짐.
- 하지만 이러한 순차적 흐름만으로는 현실 세계의 복잡한 조건이나 반복적인 상황을 해결할 수 없음.
- 현실적인 문제 상황:
- 조건에 따른 분기: 사용자 로그인 여부에 따라 다른 화면을 보여주거나, 성적 점수에 따라 학점을 다르게 부여해야 하는 상황이 존재함.
- 동일 작업의 반복: 회원 1만 명에게 단체 메일을 발송하거나, 구구단을 출력하는 등 똑같은 코드를 무수히 많이 실행해야 하는 상황이 존재함.
- 제어문(Control Flow Statements)의 역할:
- 코드의 실행 흐름을 개발자가 원하는 대로 통제하고 바꿀 수 있도록 만드는 무기임.
- 특정 조건에 따라 실행할 코드를 건너뛰거나(조건문), 원하는 만큼 코드를 뱅글뱅글 돌며 재실행(반복문)할 수 있게 제어권을 부여함.
if문 (조건문) — 조건이 참일 때만
- if문의 개념:
- 조건식의 참(
true) 또는 거짓(false) 여부에 따라 특정 코드 블록의 실행 여부를 결정하는 가장 기본적인 제어문임. - 오직 조건식이
true일 때만 중괄호({ }) 안의 코드가 실행되며,false일 경우에는 코드 블록을 실행하지 않고 통째로 건너뜀.
- 조건식의 참(
- 기본 문법 구조:
if (조건식) { // 조건식이 true일 때 실행할 코드 }구조를 가짐.- 조건식 자리에는 반드시 연산 결과가
boolean형(true또는false)으로 나오는 비교 연산이나 논리 연산이 들어가야 함.
- 실행 흐름과 주의 사항:
- 중괄호 생략 가능(비권장): 실행할 문장이 단 한 줄뿐이라면 중괄호
{ }를 생략할 수 있으나, 코드의 가독성이 떨어지고 추후 코드를 추가할 때 버그가 발생할 위험이 높아지므로 무조건 중괄호를 작성하는 것이 원칙임. - 코드 예시:
if (score >= 90) { System.out.println("합격입니다."); }➡️ 점수가 90점 이상인 조건이 참일 때만 합격 문구를 콘솔에 출력함.
- 중괄호 생략 가능(비권장): 실행할 문장이 단 한 줄뿐이라면 중괄호
import java.util.Scanner;
public class IfBasic {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("점수를 입력하세요: "); // 사용자에게 입력 안내
int score = sc.nextInt(); // 정수 입력 받기
if (score >= 60) { // 60점 이상이면 -> 참
System.out.println("합격입니다."); // 참일 때만 실행
}
System.out.println("프로그램 종료"); // if 밖 -> 항상 실행
}
}
if-else문 (조건 분기) — 둘 중 하나는 반드시
- if-else문의 개념:
- 조건식이 참(
true)일 때와 거짓(false)일 때 실행할 코드 블록을 각각 나누어 정의하는 양방향 분기 제어문임. - 조건에 따라 “이것 아니면 저것” 중 반드시 하나의 블록만 선택하여 실행하고, 두 블록이 동시에 실행되는 경우는 절대 없음.
- 조건식이 참(
- 기본 문법 구조:
if (조건식) { // 조건식이 true일 때 실행할 코드 } else { // 조건식이 false일 때 실행할 코드 }구조를 가짐.else뒤에는 조건식을 작성하지 않으며,if문의 조건이 맞지 않는 그 외의 모든 예외 상황을 통틀어 처리함.
- 실행 흐름과 장점:
- 효율성 증대: 두 개의 독립된
if문을 사용하는 것보다 연산 횟수를 줄일 수 있음.if조건이 참이면else블록은 아예 확인도 하지 않고 건너뛰기 때문임. - 코드 예시:
- 효율성 증대: 두 개의 독립된
import java.util.Scanner;
public class EvenOdd {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("정수를 입력하세요: ");
int n = sc.nextInt();
if (n % 2 == 0) { // 2로 나눈 나머지가 0이면
System.out.println(n + "은(는) 짝수입니다.");
} else { // 그 외(나머지가 1)
System.out.println(n + "은(는) 홀수입니다.");
}
}
}
else-if 사다리 (다중 분기)
- else-if문의 개념:
- 세 개 이상의 여러 갈래길(조건) 중 하나를 선택해야 할 때 사용하는 다중 조건 제어문임.
- 위에서부터 순차적으로 조건을 검사하며, 최초로 참(
true)이 되는 단 하나의 코드 블록만 실행하고 전체 문장을 빠져나감.
- 기본 문법 구조:
if (조건식1) { ... } else if (조건식2) { ... } else { ... }구조를 취함.else if는 제한 없이 여러 번 추가할 수 있으며, 마지막의else는 모든 조건이false일 때의 예외 처리를 담당하고 생략도 가능함.
- 실행 흐름과 주의 사항:
- 순서의 중요성: 상위 조건이 먼저 참이 되면 하위 조건은 검사조차 하지 않으므로, 범위가 좁거나 엄격한 조건을 위쪽에 먼저 배치해야 함.
import java.util.Scanner;
public class Grade {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("점수를 입력하세요(0~100): ");
int score = sc.nextInt();
if (score >= 90) { // 90 이상이면 A
System.out.println("학점: A");
} else if (score >= 80) { // 80 이상 89 이하 → B
System.out.println("학점: B");
} else if (score >= 70) {
System.out.println("학점: C");
} else if (score >= 60) {
System.out.println("학점: D");
} else { // 그 외(0~59) → F
System.out.println("학점: F");
}
}
}
if 초보자가 만나는 4가지 함정
- ⚠️ 1. 세미콜론의 저주 (
if (...);):- 현상: 조건식이 참이든 거짓이든 상관없이 중괄호 내부의 코드가 무조건 실행되는 치명적인 논리 오류가 발생함.
- 원인:
if (조건식)뒤에 실수로 세미콜론;을 붙이면, 컴파일러는 이를 “아무것도 하지 않는 빈 문장”으로 인식하여 조건문의 범위를 거기서 끝내버리기 때문임. - 방어선:
if문의 소괄호 직후에는 절대로 세미콜론;을 찍지 않는 습관을 들여야 함.
- 🔗 2. 대입과 비교의 혼동 (
if (x = y)):- 현상: 두 값이 같은지 비교하려 했으나 컴파일 에러가 발생하거나, 엉뚱한 조건 분기가 실행됨.
- 원인: 자바에서 값이 같은지 비교할 때는 비교 연산자
==를 써야 하지만, 값을 대입하는 연산자인=를 단독으로 사용했기 때문임. 자바의if문 소괄호 안에는 오직boolean결과만 올 수 있으므로 정수 대입 연산은 문법 에러를 유발함. - 방어선: 동등 비교를 처리할 때는 항상 두 개의 눈 기호(
==)가 들어갔는지 눈으로 엄격하게 검증해야 함.
- 📂 3. 중괄호 생략과 들여쓰기의 착각:
- 현상: 들여쓰기(Tab)를 똑같이 맞춰두어 여러 줄이 함께 실행될 줄 알았으나, 실제로는 특정 조건에서 한 줄만 따로 실행되거나 예기치 않게 동작함.
- 원인:
if문 뒤에 중괄호{ }를 생략하면 컴파일러는 오직 직후에 나오는 딱 한 줄(하나의 문장)만 조건문에 종속시키기 때문임. 컴퓨터는 사람의 들여쓰기를 전혀 보지 않고 오직 기호로만 구역을 판별함. - 방어선: 단 한 줄의 실행문만 작성하더라도 무조건 중괄호
{ }를 생략하지 않고 온전하게 감싸주는 습관이 가장 안전한 코딩 표준임.
- 📉 4. 다중 분기 조건의 순서 꼬임:
- 현상: 특정 입력 데이터가 완전히 잘못된 엉뚱한 결과 블록으로 흘러 들어가 걸러지는 논리적 버그가 발생함.
- 원인:
if - else if다중 분기 구조에서 필터링 범위가 좁고 엄격한 조건을 상단에 먼저 배치하지 않고 포괄적인 넓은 범위를 위에 배치하면, 정밀한 하위 조건문까지 도달하지 못하고 위에서 먼저 낚여 처리되기 때문임. - 방어선: 다중 조건문을 설계할 때는 반드시 ‘범위가 가장 좁고 가혹한 조건’을 맨 위에 배치하고 아래로 갈수록 넓은 조건을 배치하여 단계별로 정교하게 걸러내야 함.
Period 02 — Switch
언제 사다리, 언제 switch? — 5가지 기준
- 🎯 1. 완전한 동등 비교(
==)만 수행할 때:- 값의 크고 작음(초과, 미만, 이상, 이하)을 따지는 범위 비교가 아니라, 어떤 값이 특정 리터럴과 “정확히 일치하는지”만 판별하는 구조일 때 선택함.
switch구문은 내부적으로 범위를 계산하는 연산 식을 넣을 수 없기 때문임.
- 📊 2. 분기할 케이스가 고정되어 있고 많을 때:
- 처리해야 하는 조건의 가짓수가 3~4개 이상으로 많아져서
if - else if구조를 연속으로 쓰면 코드가 지저분해지고 가독성이 심각하게 떨어질 때 유용함. - 명확하게 정형화된 선택지들이 나열되는 형태에 가장 적합함.
- 처리해야 하는 조건의 가짓수가 3~4개 이상으로 많아져서
- 🏷️ 3. 제한된 타입의 데이터 변수를 비교할 때:
- 자바의
switch문은 모든 자료형을 지원하지 않으므로 사용 가능한 특정 타입으로만 조건 비교를 수행할 때 사용함. - 정수형(
byte,short,char,int), 문자열(String), 그리고 열거형(Enum) 데이터만case라벨로 등록할 수 있으며, 실수형(float,double)이나 논리형(boolean)은 절대 불가능함.
- 자바의
- ⚙️ 4. O(1) 수준의 고속 연산 최적화가 필요할 때:
if-else구조는 최악의 경우 맨 아래 조건을 만족할 때까지 위에서부터 모든 연산식을 한 줄씩 순차적으로 실행해야 하므로 속도가 $O(N)$으로 떨어짐.- 반면
switch문은 컴파일 시점에 내부적으로 점프 테이블(Jump Table)을 생성하기 때문에, 조건 가짓수가 아무리 많아도 단 한 번에 해당case위치로 다이렉트 도약하여 연산 속도가 극도로 빠름.
- 🧩 5. 값에 따라 명확하게 떨어지는 설계(도메인)일 때:
- 요일(월~일), 월(1~12월), 회원 등급(VIP, GOLD, SILVER), 키보드 입력 키, 프로그램 명령 메뉴 등 데이터 자체가 이미 몇 가지 카테고리로 깔끔하게 상수로 분류되어 정의되는 도메인을 코드로 옮길 때 가장 직관적임.
switch — case • break • default
- 🏷️ case 라벨 (조건 분기점):
switch문으로 들어온 평가 대상 변수의 값과 일치하는지 비교하는 기준 값 리스트임.case 값:형태로 작성하며, 리터럴(정수, 문자, 문자열)이나 상수만 올 수 있고 변수나 조건식은 올 수 없음.- 입력값과
case뒤의 값이 정확히 일치하면 해당 위치로 실행 흐름이 다이렉트로 점프함.
- 🛑 break문 (제어문 탈출기):
- 현재 실행 중인
switch블록을 즉시 빠져나가도록 명령하는 제어 키워드임. - 각
case블록의 마지막에break;가 없으면, 아래에 있는 다른case의 코드까지 무조건 연달아 실행하는 Fall-Through(낙하) 현상이 발생하므로 논리적 설계가 아니라면 반드시 포함해야 함.
- 현재 실행 중인
- 🔄 default 라벨 (나머지 예외 처리):
- 위에서 나열한 그 어떤
case라벨과도 일치하는 값이 없을 때, 최후에 선택되어 무조건 실행되는 예외 처리용 블록임. if-else구조에서 마지막else와 일치하는 역할을 담당함.switch문 내부 어디에나 위치할 수 있으나 보통 관례상 가장 맨 아래에 배치하며, 맨 마지막에 배치하는 경우 뒤에 실행할 코드가 없으므로break;를 생략하는 것이 일반적임.
- 위에서 나열한 그 어떤
import java.util.Scanner;
public class DayName {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("요일 번호(1~7): ");
int day = sc.nextInt();
switch (day) { // day 변수의 값으로 분기
case 1:
System.out.println("월요일");
break; // 다음 case로 안 흘러내림
case 2:
System.out.println("화요일");
break;
case 3:
System.out.println("수요일");
break;
case 4:
System.out.println("목요일");
break;
case 5:
System.out.println("금요일");
break;
case 6:
case 7: // 6과 7은 묶음 (의도된 fall-through)
System.out.println("주말");
break;
default: // 1~7이 아닌 모든 값
System.out.println("잘못된 번호");
}
}
}
switch expression — 화살표(→)로 깔끔하게
- 🚀 Java 14+ 신문법의 도입:
- 기존의 전통적인
switch문이 가졌던 가독성 저하와break;누락으로 인한 버그 가능성을 완벽히 해결하기 위해 도입된 모던 자바의 문법임. switch문 자체가 하나의 값(Expression)으로 평가되므로 연산 결과를 변수에 다이렉트로 대입하거나 인자로 넘길 수 있는 혁신적인 특징을 가짐.
- 기존의 전통적인
- 🏹 화살표 연산자 (
>) 도입:- 기존의 콜론(
:) 대신 화살표(>) 기호를 사용하여 해당 케이스가 매치되었을 때 실행할 코드나 반환할 값을 직관적으로 매핑함. - 화살표 문법을 사용하면 매칭된 단 하나의 케이스만 정밀하게 실행하고 자동으로 switch 블록을 탈출하므로, 옛 문법처럼 구구절절
break;를 적지 않아도 됨.
- 기존의 콜론(
- 🧩 다중 라벨 지원과 가독성 향상:
- 동일한 실행 결과를 공유하는 여러
case들을 처리할 때, 구차하게 줄바꿈으로 나열할 필요 없이 쉼표(,)를 사용하여 한 줄에 묶어 선언할 수 있음. - 예시:
case 6, 7 -> "주말";형태로 작성하여 가독성을 극대화함.
- 동일한 실행 결과를 공유하는 여러
- 🧱 값의 변수 즉시 대입 (표현식 변환):
switch내부에서 연산된 최종 결과물이 통째로 튀어나오기 때문에 오른쪽 구문 전체를 변수의 초기화 값으로 사용할 수 있음.- 예시:
String name = switch(day) { ... };구조로 사용되며, 표현식 문장 끝에는 반드시 세미콜론;을 붙여서 구문의 마무리를 명시해야 함.
import java.util.Scanner;
public class DayNameExpr {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("요일 번호(1~7): ");
int day = sc.nextInt();
// switch가 값(String)을 만든다 → 변수에 바로 대입
String name = switch (day) {
case 1 -> "월요일";
case 2 -> "화요일";
case 3 -> "수요일";
case 4 -> "목요일";
case 5 -> "금요일";
case 6, 7 -> "주말"; // 다중 라벨, break 불필요
default -> "잘못된 번호";
};
System.out.println("결과: " + name);
}
}
case는 시작점만 찾는다 — Fall-Through
- 🔄 시작점 탐색 메커니즘:
- 전통적인
switch문은 변수의 값과 일치하는case라벨을 내부적으로 단 한 번 가동하는 ‘실행의 시작 위치(Entry Point)’로만 인식함. - 조건이 일치하는 위치를 찾으면 컴퓨터는 그 지점부터 스위치 블록 끝까지 아래로 계속 직진하며 연산을 처리함.
- 전통적인
- ⚠️ Fall-Through (아래로 흘러내림) 현상:
- 현상: 최초로 일치한
case내부의 구문이 실행된 이후, 그 아래에 배치된 다른case와default블록까지 아무런 제어 없이 무조건 관통하며 연달아 실행됨. - 원인: 컴퓨터는 아래로 직진하려는 성질을 가지고 있어서, 도중에 흐름을 끊어주는 제어 키워드인
break;를 발견할 때까지 연산을 멈추지 않기 때문임.
- 현상: 최초로 일치한
- 🧱 의도된 사용법 vs 대표적 실수:
- 의도된 활용 (다중 라벨 트릭): 여러 조건이 완전히 동일한 실행 결과물로 수렴해야 할 때 일부러
break;를 빼서 코드를 결합하는 기법임. (예:case 6:과case 7:을 연달아 두고 아래에 주말 문구를 배치). - 치명적 실수: 단순 코드 수정이나 방심으로 인해
break;가 누락되면 사용자는 단 하나의 결과물만 원했음에도 엉뚱한 로그가 콘솔에 줄줄이 누적 출력되는 심각한 논리 에러를 맞닥뜨리게 됨.
- 의도된 활용 (다중 라벨 트릭): 여러 조건이 완전히 동일한 실행 결과물로 수렴해야 할 때 일부러
종합 실습 — 계절 분류기
import java.util.Scanner;
public class Season {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("월(1~12): ");
int m = sc.nextInt();
String season = switch (m) {
case 3, 4, 5 -> "봄";
case 6, 7, 8 -> "여름";
case 9, 10, 11 -> "가을";
case 12, 1, 2 -> "겨울";
default -> "잘못된 월";
};
System.out.println(m + "월은 " + season);
}
}
Period 03 — for 반복문
for의 3구역 분해 — 한 줄에 모든 것이 있다
- 🏗️ for문의 구조적 정의:
- 반복 횟수가 명확할 때 주로 사용하는 자바의 대표적인 반복문 제어 규칙임.
- 소괄호
for( ... )내부에 세미콜론(;)을 구분자로 삼아 역할을 완벽히 분리한 초기화식, 조건식, 증감식의 3가지 핵심 구역을 체계적으로 밀집시켜 놓은 형태임.
- 🧩 3구역의 상세 역할과 분해:
- 1구역 — 초기화식 (Initialization):
- 반복문이 스타트를 끊을 때 단 한 번만 최초로 실행되는 공간임.
- 카운팅 변수(루프 제어 변수)를 선언하고 초기값을 할당하는 용도로 쓰임. (예:
int i = 0)
- 2구역 — 조건식 (Condition):
- 매 반복(루프)이 시작되기 직전마다 실행 내용을 검사하는 관문임.
- 연산 결과가
true일 때만 중괄호 내부의 본문 코드를 실행하고,false가 되는 순간 즉시for문을 가차 없이 탈출함. (예:i < 10)
- 3구역 — 증감식 (Increment/Decrement):
- 중괄호 안의 반복 본문 코드가 모두 실행되어 바닥에 도달했을 때 마지막에 호출되는 구역임.
- 루프 변수의 값을 증가시키거나 감소시켜 조건식의 결과를
false방향으로 유도하여 루프가 무한히 도는 것을 방지함. (예:i++)
- 1구역 — 초기화식 (Initialization):
- 🔄 3구역의 엄격한 실행 순서 흐름:
- ① 초기화식 가동 (최초 1회만) ➡️ ② 조건식 검사 (참이면 통과) ➡️ ③ for문 본문 코드 실행 ➡️ ④ 증감식 실행 ➡️ ⑤ 다시 ②번 조건식 검사 단계로 돌아가 참인 동안 계속 순환함.
1~100합 — 누적 변수 패턴 (Accumulator Pattern)
- 🏗️ 누적 변수 패턴의 정의:
- 반복문이 실행되는 동안 발생하는 여러 데이터나 연산 결과를 한곳에 지속적으로 더하거나 곱하여 최종 합계 및 결과물을 만들어내는 프로그래밍 기법임.
- 자바에서 전체 합계, 팩토리얼(곱셈), 문자열 결합 등을 처리할 때 반드시 사용되는 가장 표준적인 패턴임.
- 🧩 패턴을 구성하는 3대 핵심 요소:
- 1. 누적 보관 상자 (변수 선언 및 초기화):
- 반복문이 시작되기 직전(반복문 외부)에 연산 결과를 누적해서 담아둘 변수를 반드시 먼저 만들어야 함.
- 누적 방식에 따라 초기값이 결정됨. 덧셈 누적은 복합 연산에 영향을 주지 않는 0으로, 곱셈 누적은 1로 설정함. (예:
int sum = 0;)
- 2. 루프 카운터 구동 (반복문 제어):
- 특정 범위나 조건을 순차적으로 훑고 지나갈 수 있도록
for문 또는while문 등의 제어 장치를 설계함. (예:for (int i = 1; i <= 100; i++))
- 특정 범위나 조건을 순차적으로 훑고 지나갈 수 있도록
- 3. 누적 연산 장치 (복합 대입 연산자):
- 반복문 내부(본문)에서 루프 변수의 값을 보관 상자에 계속해서 집어넣음.
- 기존 변수의 값에 새로운 값을 더해 다시 대입하는
sum = sum + i구조를 압축한 복합 대입 연산자(+=,=)를 주로 사용함. (예:sum += i;)
- 1. 누적 보관 상자 (변수 선언 및 초기화):
public class SumTo100 {
public static void main(String[] args) {
int sum = 0; // 합을 누적할 변수, 0으로 초기화
for (int i = 1; i <= 100; i++) { // i=1부터 100까지 1씩 증가
sum += i; // sum = sum + i
}
System.out.println("1~100 합 = " + sum); // 5050
}
}
구구단 — 단(段)을 변수로
import java.util.Scanner;
public class Gugudan {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("출력할 단(2~9): ");
int dan = sc.nextInt();
System.out.println("=== " + dan + "단 ===");
for (int i = 1; i <= 9; i++) { // i: 곱하는 수
System.out.printf("%d × %d = %d%n", dan, i, dan * i);
}
}
}
for(…); 세미콜론 한 글자가 모든 걸 망친다
- ⚠️ for문 직후 세미콜론(
;) 삽입 함정:- 현상: 반복 조건과 무관하게 중괄호
{ }블록 내부의 코드가 단 한 번만 실행되고 끝나거나, 루프 제어 변수를 찾지 못하는 컴파일 에러가 발생함. - 원인:
for(...);처럼 소괄호 끝에 세미콜론을 붙이면, 컴파일러는 이를 ‘아무것도 실행하지 않는 빈 문장(Null Statement)’이 반복되는 것으로 인식하기 때문임.
- 현상: 반복 조건과 무관하게 중괄호
- 🧱 컴파일러가 해석하는 논리적 분리:
- 개발자가 의도한 구조:
for조건 가동 ➡️ 중괄호 블록 전체를 N번 반복 실행. - 컴퓨터가 오해한 구조:
for조건 가동 ➡️ 빈 문장(;)을 N번 반복 실행하고 종료 ➡️ 밑에 남겨진 중괄호{ }블록은 반복문과 아무 상관 없는 독립적인 일반 코드로 딱 1회성 실행.
- 개발자가 의도한 구조:
- 📉 변수 스코프(Scope) 유실 에러:
- 중괄호 본문 안에서 루프 카운팅 변수(예:
i)를 사용하고 있을 경우, 반복문이 이미 세미콜론 지점에서 완결되어 소멸했기 때문에 밑에 있는 독립 블록에서는i라는 변수의 존재를 인지하지 못해 “Cannot find symbol” 에러를 뿜어냄.
- 중괄호 본문 안에서 루프 카운팅 변수(예:
거꾸로 • 짝수만 • 2씩 — 같은 for, 다른 흐름
- 🔄 1. 거꾸로 패턴 (역순 루프):
- 정의: 큰 숫자부터 시작하여 작은 숫자로 하나씩 감소하며 범위를 탐색하는 루프 제어 기법임.
- 메커니즘: 초기화식에 최댓값을 넣고, 조건식에 최솟값 기준(이상, 초과)을 설정한 뒤, 증감식에 감소 연산자(
-)를 배치함. - 예시:
for (int i = 10; i > 0; i--)➡️ 10부터 1까지 거꾸로 카운트다운을 수행함.
- 🔢 2. 짝수만 패턴 (조건부 필터링 루프):
- 정의: 주어진 전체 범위 내에서 짝수(또는 홀수) 데이터만 선별하여 본문 코드를 가동하는 연산 방식임.
- 메커니즘:
for문 내부에if조건문을 결합하여, 루프 변수를 2로 나눈 나머지 값이 0인 경우(i % 2 == 0)만 동작하도록 이중 필터를 구축함. - 예시:
for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { ... } }➡️ 1부터 10까지 돌며 짝수일 때만 내부 로직을 태움.
- ⚡ 3. 2씩 패턴 (고속 점프 루프):
- 정의:
if조건문 없이 증감식 자체를 개조하여 루프 변수의 증가 폭을 제어하는 고속 최적화 루프 기법임. - 메커니즘: 매 루프가 끝날 때마다 1씩 증가하는
i++대신, 복합 대입 연산자i += 2를 사용하여 변수 값을 한 번에 2씩 건너뛰게 만듦. - 예시:
for (int i = 2; i <= 10; i += 2)➡️ 처음부터 짝수로 시작해 2씩 점프하므로, 불필요한 홀수 루프를 생성 및 연산하지 않아 짝수만 패턴보다 연산 속도가 2배 빠름.
- 정의:
public class ForVariants {
public static void main(String[] args) {
// 1) 거꾸로 카운트
System.out.print("Countdown: ");
for (int i = 10; i >= 1; i--) {
System.out.print(i + " "); // 10 9 8 ... 1
}
System.out.println();
// 2) 짝수만 (증감으로)
System.out.print("Evens 0~20: ");
for (int i = 0; i <= 20; i += 2) {
System.out.print(i + " "); // 0 2 4 ... 20
}
System.out.println();
// 3) 1~50 중 3의 배수 (if 조합)
System.out.print("Multiples of 3: ");
for (int i = 1; i <= 50; i++) {
if (i % 3 == 0) { // i가 3의 배수면 출력
System.out.print(i + " ");
}
}
System.out.println();
}
}
1 차이로 결과가 어긋난다 — Off-By-One
- ⚠️ 오프바이원(Off-By-One) 에러의 정의:
- 반복문 설계 시 경계선 조건을 정밀하게 계산하지 못해, 루프가 의도한 것보다 딱 한 번 더 돌거나 정작 한 번 덜 돌아서 연산 결과가 어긋나는 대표적인 논리적 버그(Bug)임.
- 프로그램이 뻗어버리는 컴파일 에러가 아니라 결과값만 미세하게 틀리기 때문에, 찾아내기가 매우 까다로운 함정임.
- 🔍 버그가 발생하는 원인 분해:
- 이상/이하(
<=,>=)와 초과/미만(<,>)의 혼동: 반복 범위를 지정하는 조건식 기호를 무심코 잘못 선택하여 1 차이의 균열이 발생함. - 시작 인덱스(0 또는 1)의 설정 오류: 자바의 배열이나 문자열처럼 0번 인덱스부터 시작하는 도메인을 다룰 때, 반복 제어 변수의 초기값을 1로 잡거나 끝 범위를 길이에 맞춰 지정하면서 꼬이게 됨.
- 이상/이하(
- 📉 구체적인 실패 유형 예시:
- 1번 덜 도는 경우 (Under-run): 1부터 10까지 총 10번을 누적하고 싶었으나,
for(int i=1; i<10; i++)형태로 조건식을 주어 10이 빠진 9번만 더해져 최종 합계가 틀림. - 1번 더 도는 경우 (Over-run): 크기가 5인 배열(
arr)을 0번부터 4번 인덱스까지 훑어야 하는데, 조건식을for(int i=0; i<=5; i++)로 설정하여 존재하지 않는 5번 인덱스를 들이받고ArrayIndexOutOfBoundsException예외를 터뜨림.
- 1번 덜 도는 경우 (Under-run): 1부터 10까지 총 10번을 누적하고 싶었으나,
- 💡 오프바이원 예방을 위한 방어선:
- 루프 변수가 처음 스타트를 끊는 ‘시작값’과 루프를 빠져나가는 ‘끝 경계값’의 상태를 머릿속으로 시뮬레이션하며 수동으로 한 번씩 검증해 보아야 함.
- 자바 배열을 순회할 때는
for (int i = 0; i < arr.length; i++)구조를 표준 공식처럼 사용하여 이상/이하 기호와 길이 수치가 충돌하는 현상을 원천 차단하는 것이 좋음.
Period 04 — while / do-while
for • while • do-while — 언제 무엇을
| 반복문 종류 | 핵심 선택 기준 (언제 쓰는가?) | 최초 실행 보장 | 주요 활용 예시 |
|---|---|---|---|
for문 | • 반복 횟수가 명확하게 정해져 있을 때 | ||
| • 인덱스나 카운터 변수를 기반으로 순회할 때 | 조건식이 처음부터 false이면 0회 (실행 안 됨) | • 1부터 100까지의 합 구하기 | |
| • 구구단 출력, 배열 및 컬렉션 전체 순회 | |||
while문 | • 반복 횟수를 예측할 수 없을 때 | ||
| • 특정 조건이 만족되는 동안 계속 돌려야 할 때 | 조건식이 처음부터 false이면 0회 (실행 안 됨) | • 사용자가 “exit”을 입력할 때까지 무한 대기 | |
| • 파일의 끝(EOF)까지 데이터 읽어 들이기 | |||
do-while문 | • 조건 검사보다 본문 실행이 먼저 일어나야 할 때 | ||
| • 조건에 상관없이 최소 1회 실행을 보장해야 할 때 | 조건식 결과와 무관하게 무조건 최소 1회 실행 | • 콘솔 메뉴판을 먼저 출력한 후 사용자 입력받기 | |
| • 키보드 입력을 먼저 받고 유효성 검사하기 |
while — 종료어 입력까지 반복
- 🏗️ 종료어 판단 반복의 개념:
- 몇 번을 돌려야 할지 횟수는 컴파일 시점에 미리 알 수 없으나, 특정 문자열(예:
"exit")이 입력되는 것을 기준으로 루프를 영리하게 제어하는 가변적 반복 패턴임. - 사용자 인터랙션, 메뉴 선택 시스템, 실시간 명령어 입력기 등을 설계할 때 반드시 정복해야 하는 자바
while문의 대표적인 표준 활용 유형임.
- 몇 번을 돌려야 할지 횟수는 컴파일 시점에 미리 알 수 없으나, 특정 문자열(예:
- 🧩 핵심 로직과 자바 문법 메커니즘:
- 루프 조건식의 역전 활용: 종료 조건이
"exit"인 경우, 반복문은 “exit이 아닐 때만 계속 돈다”라는 반대 논리를 세워 제어식을 작성해야 함. - 문자열 비교 주의사항 (
.equals()): 자바에서 기본형 변수처럼input != "exit"와 같이 비교 연산자(!=)를 사용하면 주소값 비교가 일어나 논리적 버그가 터짐. 반드시.equals()메서드를 사용하여 문자열 내용물 자체를 정밀하게 비교해야 함. - 예시 코드:
while (!input.equals("exit")) { ... }➡️ 입력값이 exit이 아니라면 조건이true가 되어 본문이 끊임없이 실행됨.
- 루프 조건식의 역전 활용: 종료 조건이
- 💻 전형적인 입력 대기 구현 흐름:
- ① 반복문 외부에서 사용자 입력을 받기 위한
Scanner객체와 문자열 변수 초기화 ➡️ ②while진입 전 첫 입력값 수집 ➡️ ③!input.equals("exit")조건 검사 ➡️ ④ 본문 비즈니스 로직 수행 ➡️ ⑤ 중괄호가 끝나기 직전(바닥)에 다음 입력을 새로 갱신하여 다시 조건 검사대로 도약.
- ① 반복문 외부에서 사용자 입력을 받기 위한
import java.util.Scanner;
public class EchoLoop {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = ""; // 초기값: 빈 문자열
System.out.println("아무거나 입력 (종료: exit)");
while (!input.equals("exit")) { // exit가 아닌 동안 반복
System.out.print("> ");
input = sc.nextLine(); // 매 회 새 입력으로 갱신
if (!input.equals("exit")) { // exit가 아니면 메아리
System.out.println("Echo: " + input);
}
}
System.out.println("종료합니다.");
}
}
do-while — 메뉴 출력 후 선택
- 🏗️ 선 실행-후 검사(Post-test Loop) 메커니즘:
- 조건식을 먼저 따지는 일반
while문과 달리, 중괄호{ }블록 내부의 코드를 무조건 최소 1회 우선 실행한 뒤 바닥에서 지속 여부를 검사하는 제어문임. - 사용자에게 무조건 안내 화면이나 선택지를 먼저 보여주어야 하는 콘솔 기반 메뉴판 시스템 디자인에 가장 직관적으로 부합하는 문법임.
- 조건식을 먼저 따지는 일반
- 🧩 핵심 구조 및 주의해야 할 문법 규격:
- 구조:
do { // 최초 1회 보장 및 반복 실행할 본문 } while (조건식); - ⚠️ 세미콜론
;필수 필수 부착: 자바의 모든 제어문 중 유일하게 맨 끝 소괄호 뒤에 세미콜론;을 반드시 찍어 구문을 마감해야 하는 특이성을 가짐. 세미콜론을 누락하면 컴파일 에러가 발생함.
- 구조:
- 💻 전형적인 콘솔 메뉴 선택 구현 흐름:
- ①
do블록 진입: 화면에 프로그램 메뉴(1. 등록, 2. 조회, 3. 종료 등)를 콘솔에 출력함. - ② 사용자 입력 수집: 사용자가 원하는 번호나 명령을 키보드로 입력받아 변수에 대입함.
- ③ 비즈니스 로직 수행: 입력된 번호에 따라 필요한 기능을 실행함.
- ④ 바닥 조건 검사:
while (choice != 3);과 같이 종료 번호가 아닐 때 위로 다시 올려보내도록 탈출 조건을 평가함. 3번을 누르는 순간 조건이false가 되어 루프를 탈출함.
- ①
- 💡 do-while문이 선택되는 결정적 이유:
- 일반
while문을 쓰려면 반복문 내부로 진입하기 위해 변수를 억지로 임의의 값으로 초기화하거나 문 밖에 메뉴 출력 코드를 중복해서 중구난방 작성해야 함. do-while을 활용하면 코드의 중복을 완벽히 제거하고 “화면 표시 ➡️ 입력 ➡️ 조건 판단”의 자연스러운 인간의 행동 흐름대로 깔끔하게 코딩할 수 있음.
- 일반
import java.util.Scanner;
public class MenuDoWhile {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int choice;
do {
System.out.println("\n=== 메뉴 ===");
System.out.println("1. 인사");
System.out.println("2. 날짜");
System.out.println("0. 종료");
System.out.print("선택: ");
choice = sc.nextInt(); // 최소 1번은 반드시 입력
switch (choice) {
case 1 -> System.out.println("안녕하세요!");
case 2 -> System.out.println("오늘은 좋은 날입니다.");
case 0 -> System.out.println("종료합니다.");
default -> System.out.println("잘못된 선택");
}
} while (choice != 0); // 0이 아닌 동안 반복
}
}
i++ 한 줄을 빼먹으면 영원히 1만 출력 (무한 루프)
- ⚠️ 증감식 누락으로 인한 무한 루프(Infinite Loop):
- 현상: 프로그램이 종료되지 않고 콘솔 창에 똑같은 결과(예: 숫자
1)가 끊임없이 스크롤되며 출력되거나, CPU 점유율이 100%까지 치솟으며 시스템이 먹통이 됨. - 원인: 반복 조건의 기준이 되는 루프 제어 변수(예:
i)를 증가시키거나 감소시키는 증감식(i++) 코드를 한 줄 누락했기 때문임.
- 현상: 프로그램이 종료되지 않고 콘솔 창에 똑같은 결과(예: 숫자
- 🧱 조건식의 영원한 참(
true) 상태:while (i <= 10)구문은i가 10 이하인 동안 계속 돌도록 설계되어 있음.- 최초에
i = 1로 시작한 상태에서 본문 내부에i++가 없다면,i는 루프가 아무리 살아 움직여도 영원히 초기값인1에 고정됨. - 결과적으로 조건식
1 <= 10은 평생true가 되므로 컴퓨터는 탈출구를 찾지 못하고 웅덩이에 빠진 것처럼 내부 본문만 무한히 실행하게 됨.
- 💻 치명적인 코드 예시 및 올바른 구조:
실패형 (무한 루프):
int i = 1; while (i <= 10) { System.out.println(i); // 증감식이 없어서 i는 영원히 1 상태, 무한 출력 발생 }성공형 (정상 종료):
int i = 1; while (i <= 10) { System.out.println(i); i++; // 이 한 줄이 있어야 i가 11이 되는 순간 조건이 false가 되어 탈출함 }
while vs do-while — 검사 위치의 차이
- 🔍 핵심 차이점 — 조건 검사 위치 (Pre-test vs Post-test):
while문 (선 검사 / 루프 진입 전 평가):- 조건식이 중괄호 블록의 맨 위(진입로)에 위치함.
- 루프 본문 코드를 실행하기 전에 조건을 먼저 검사하므로, 최초 검사에서 조건이
false이면 본문 코드는 단 한 번도 실행되지 않고 통째로 건너뜀 (최소 0회 실행 보장).
do-while문 (후 검사 / 루프 실행 후 평가):- 조건식이 중괄호 블록의 맨 아래(출구)에 위치함.
- 조건의 참/거짓 여부를 따지기 전에 본문 코드를 무조건 먼저 들이받고 실행하기 때문에, 초기 조건이
false이더라도 무조건 최소 한 번은 실행됨 (최소 1회 실행 보장).
- 🔄 실행 흐름 비교:
while: 조건식 검사 ➡️ (참일 때만) ➡️ 본문 코드 실행 ➡️ 다시 위로 이동해 조건식 검사.do-while: 본문 코드 실행 ➡️ 바닥에서 조건식 검사 ➡️ (참이면) ➡️ 다시 위로 이동해 본문 코드 실행.
while = 클럽 도어맨(자격 미달이면 입장 불가) · do-while = 시식 코너(먼저 맛본 후 살지 말지 결정). 한 줄의 위치가 정반대 동작을 만든다.
Period 05 — 중첩 반복 & break/continue
직각 삼각형 (이중 for문 제어)
- 🏗️ 직각 삼각형 출력의 핵심 개념:
- 중첩 반복문(이중
for문)을 활용하여 콘솔 창에 행과 열 구조로 별()을 배치, 직각 삼각형 모양을 시각화하는 알고리즘 패턴임. - 바깥쪽
for문이 ‘행(줄)’을 제어하고, 안쪽for문이 ‘열(칸, 별의 개수)’을 제어하는 이중 루프 구조를 가짐.
- 중첩 반복문(이중
- 🧩 가변적 조건 제어 메커니즘:
- 고정형 사각형과의 차이: 가로·세로가 일정한 정사각형은 안쪽 루프의 종료 조건이 상수(예:
j < 5)로 고정되지만, 직각 삼각형은 아래로 내려갈수록 별의 개수가 늘어나야 하므로 조건이 유동적이어야 함. - 동적 조건 설계: 안쪽
for문의 종료 조건식에 바깥쪽 루프의 제어 변수(행 번호)를 연동함. 행 번호가 커짐에 따라 안쪽 루프의 반복 횟수도 자연스럽게 동기화되어 늘어나는 원리임.
- 고정형 사각형과의 차이: 가로·세로가 일정한 정사각형은 안쪽 루프의 종료 조건이 상수(예:
- 🔄 줄바꿈 연산의 타이밍 주의선:
- 별을 찍는
System.out.print("*")는 안쪽 루프 내부에 위치해야 하며, 줄을 바꾸는System.out.println()은 반드시 안쪽 루프가 완전히 종료된 직후이자 바깥쪽 루프가 끝나기 직전(바닥)에 배치해야 직각 구조가 정상적으로 성립됨.
- 별을 찍는
public class TriangleStar {
public static void main(String[] args) {
int n = 5; // 5행짜리 삼각형
for (int i = 1; i <= n; i++) { // 행: 1, 2, 3, 4, 5
for (int j = 1; j <= i; j++) { // 열: i개 만큼 별
System.out.print("*");
}
System.out.println(); // 행 끝 줄바꿈
}
}
}
피라미드 — 공백 + 별 두 단계
- 🏗️ 피라미드 출력의 핵심 개념:
- 중첩 반복문(이중
for문)을 한 단계 더 확장하여, 콘솔 창에 좌우 대칭 형태의 삼각형(피라미드) 모양으로 별()을 배치하는 알고리즘 패턴임. - 직각 삼각형과 달리 별을 찍기 전에 ‘왼쪽 공백(스페이스)’을 먼저 정밀하게 제어해야 모양이 허물어지지 않고 가운데로 정렬됨.
- 중첩 반복문(이중
- 🧩 공백과 별의 동적 규칙성:
- 피라미드를 만들기 위해서는 줄(행)이 내려갈수록 공백은 줄어들고, 별의 개수는 홀수(1, 3, 5, 7…)로 늘어나는 이중 규칙성을 코드로 구현해야 함.
- 총 $N$줄짜리 피라미드를 만들 때, $i$번째 행의 규칙성:
- 공백의 개수: $N - i$개
- 별의 개수: $2 \times i - 1$개
- 🔄 다중 루프 배치 주의선:
- 공백을 출력하는
for문과 별을 출력하는for문은 서로 중첩되는 것이 아니라, 바깥쪽for문 내부에서 순차적으로 독립되어 실행되어야 함. 공백이 먼저 다 찍힌 다음에 이어서 별이 붙어야 하기 때문임.
- 공백을 출력하는
public class Pyramid {
public static void main(String[] args) {
int n = 5;
for (int i = 1; i <= n; i++) {
// 1) 공백 (n - i)개
for (int s = 1; s <= n - i; s++) {
System.out.print(" ");
}
// 2) 별 (2i - 1)개
for (int j = 1; j <= 2 * i - 1; j++) {
System.out.print("*");
}
System.out.println(); // 행 끝 줄바꿈
}
}
}
break • continue • return — 같은 듯 다른 셋
- 🛑 1. break (반복문 탈출):
- 동작: 자신이 포함된 가장 가까운 반복문(
for,while,do-while) 또는switch블록을 즉시 완전히 빠져나감. - 흐름:
break를 만나는 순간 남은 반복 횟수와 상관없이 루프를 종료하고, 반복문 중괄호{ }바로 다음 줄로 실행 흐름이 이동함. - 목적: 특정 조건이 만족되었을 때 불필요한 반복 연산을 조기에 중단하고 빠져나오기 위해 사용함.
- 동작: 자신이 포함된 가장 가까운 반복문(
- 🔄 2. continue (다음 반복으로 점프):
- 동작: 현재 회차의 남은 본문 코드를 모두 무시하고, 반복문의 다음 회차(증감식 또는 조건식)로 즉시 점프함.
- 흐름: 루프 자체를 끝내지는 않으며,
for문의 경우 증감식으로 이동하고while문의 경우 조건식 검사 단계로 곧바로 이동함. - 목적: 특정 예외 조건에 해당하는 데이터를 건너뛰고(Skip) 다음 연산을 계속 진행하고 싶을 때 사용함.
- ↩️ 3. return (메서드 종료 및 복귀):
- 동작: 현재 코드가 실행 중인 메서드(함수) 자체를 즉시 종료하고, 해당 메서드를 호출했던 호출처로 제어권을 반환함.
- 흐름: 반복문 내부에
return이 위치할 경우, 반복문은 물론이고 해당 반복문을 감싸고 있는 메서드 전체가 그 즉시 파괴되어 종료됨. 반환 타입이 있을 경우 뒤에 결과값을 함께 실어 보냄. - 목적: 조건이 만족되어 메서드의 최종 목표를 달성했거나, 예외 상황에서 메서드 실행을 완전히 끝내고 싶을 때 사용함.
같은 것 같지만 빠져나가는 단위가 다르다. break = 한 루프 · continue = 한 반복 · return = 한 메서드.
continue — 1~20 중 짝수만
- 🔄 continue문의 핵심 개념:
- 반복문(
for,while,do-while) 내부에서 실행 흐름을 강제로 바꾸는 제어 키워드임. continue문을 만나는 순간, 현재 회차에서 그 아래에 남아 있는 모든 본문 코드를 과감히 무시하고 반복문의 다음 회차로 즉시 점프함.
- 반복문(
- ⚙️ 반복문 종류별 이동 위치:
for문:continue가 가동되면 즉시 3구역인 ‘증감식’으로 도약하여 루프 변수를 변화시킨 후 조건식 검사로 이동함.while/do-while문: 즉시 블록 맨 위나 아래의 ‘조건식’ 평가 단계로 다이렉트 점프함. (주의: while문에서 증감식 도달 전에 continue를 때리면 무한 루프에 빠질 수 있음)
- 💡 주요 목적과 활용선:
- 특정 조건 데이터의 필터링(Skip): 전체 데이터를 순회하는 과정에서 불필요하거나 예외적인 대상을 빠르게 제외하고 다음 연산을 이어가고 싶을 때 사용함.
- 가독성 확보 (Nest 줄이기):
if문 내부에 복잡한 실행 코드를 깊게 중첩(nesting)시키는 대신, 제외 조건을if (조건) continue;로 맨 위에 먼저 쳐내어 코드의 들여쓰기 단계를 깔끔하게 유지할 수 있음.
public class ContinueDemo {
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
if (i % 2 != 0) { // 홀수면
continue; // 출력을 건너뛰고 다음 i로
}
System.out.print(i + " "); // 짝수만 출력
}
System.out.println();
}
}
break — 소수(prime) 판별
- 🛑 break문의 핵심 개념:
- 반복문(
for,while,do-while)이나switch블록 내부에서 실행 흐름을 강제로 전면 중단시키는 제어 키워드임. break문을 만나는 순간 남은 반복 회차나 조건식의 참/거짓 여부와 상관없이 자신이 포함된 가장 가까운 반복문 하나를 즉시 탈출함.
- 반복문(
- ⚙️ 중첩 반복문(이중 루프)에서의 작동 범위:
- 단일 탈출 원칙: 바깥쪽 루프와 안쪽 루프가 겹쳐진 구조에서 안쪽 루프 내부에
break가 실행되면, 오직 안쪽 루프 한까풀만 탈출함. - 바깥쪽 루프는 여전히 살아있으므로 다음 회차의 연산이 계속 진행됨. 전체 반복문을 통째로 깨부수고 나가려면 라벨(
Label:) 문법을 사용하거나 플래그 변수를 연동해야 함.
- 단일 탈출 원칙: 바깥쪽 루프와 안쪽 루프가 겹쳐진 구조에서 안쪽 루프 내부에
- 💡 주요 목적과 활용선:
- 무한 루프의 안전장치: 의도적으로 설계한 무한 루프(
while(true)) 내부에서 특정 목표치를 달성했거나 오류가 감지되었을 때 프로그램을 안전하게 종료시키는 핵심 브레이크 장치로 쓰임. - 불필요한 연산 차단: 찾고자 하는 데이터(예: 특정 검색 대상)를 발견한 순간 루프를 조기 종료함으로써 뒷부분의 무의미한 탐색 연산을 생략하여 프로그램의 효율성을 대폭 높임.
- 무한 루프의 안전장치: 의도적으로 설계한 무한 루프(
import java.util.Scanner;
public class PrimeCheck {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("정수 입력(>=2): ");
int n = sc.nextInt();
boolean isPrime = true; // 일단 소수라고 가정
for (int i = 2; i < n; i++) {
if (n % i == 0) { // 약수 발견
isPrime = false;
break; // 더 안 봐도 됨 → 즉시 종료
}
}
if (isPrime) {
System.out.println(n + "은(는) 소수");
} else {
System.out.println(n + "은(는) 소수가 아님");
}
}
}
Period 06 — 종합 실습 (미니 계산기)
5 / 2 = 2 — 정수 나눗셈의 충격
- ⚠️ 정수형 연산의 숨겨진 규칙:
- 현상: 수학적으로
5 / 2는2.5가 되어야 하지만, 자바 프로그램 소스코드에서int타입끼리 나누기 연산을 수행하면 소수점이 통째로 잘려 나간 정수 결과값2만 출력됨. - 원인: 자바 문법 규칙상
정수(int) ⚡ 정수(int)의 산술 연산 결과는 무조건 정수(int) 타입으로 반환되기 때문임. 컴퓨터는 소수점 아래 자릿수를 저장할 공간을 임의로 만들지 않고 그대로 버림(Truncation) 처리함.
- 현상: 수학적으로
- 📉 흔히 저지르는 잘못된 해결책 (단순 타입 대입):
- 몫을 소수점까지 온전히 받기 위해 결과 변수를 실수형(
double)으로 선언하더라도 버그가 해결되지 않음. - 실패 예시:
double result = 5 / 2;➡️ 대변 수식인5 / 2가 실행되는 시점에 이미 정수형 결과인2가 도출되고, 이 값이double에 들어가면서 단순히2.0으로 변환될 뿐임.
- 몫을 소수점까지 온전히 받기 위해 결과 변수를 실수형(
- ⚡ 올바른 해결 방안 (데이터 타입 승격):
- 소수점 이하 자리까지 정밀한 계산 결과를 얻으려면, 연산에 참여하는 두 숫자 중 최소한 하나 이상을 실수 타입(
double또는float)으로 승격시켜야 함. 자바는 정수와 실수가 연산할 때 더 큰 범위인 실수형으로 자동 형변환(Type Promotion)을 수행함. - 해결 방법 1 (리터럴 개조): 숫자 뒤에 소수점을 명시함. (예:
5.0 / 2➡️ 결과:2.5) - 해결 방법 2 (강제 형변환): 변수 앞에 캐스팅 연산자
(double)를 붙여 강제로 타입을 바꿈. (예:(double)x / y➡️ 결과:2.5)
- 소수점 이하 자리까지 정밀한 계산 결과를 얻으려면, 연산에 참여하는 두 숫자 중 최소한 하나 이상을 실수 타입(
import java.util.Scanner;
public class CalculatorV2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("\n=== Mini Calculator ===");
System.out.println("1.+ 2.- 3.* 4./ 0.exit");
System.out.print("Select : ");
int choice = sc.nextInt();
if (choice == 0) break;
if (choice < 1 || choice > 4) {
System.out.println("Only enter a number between 1~4");
continue;
}
System.out.print("First Number : ");
double a = sc.nextDouble();
System.out.print("Second Number : ");
double b = sc.nextDouble();
double result = switch (choice) {
case 1 -> a + b;
case 2 -> a - b;
case 3 -> a * b;
case 4 -> {
if (b == 0) {
System.out.println("Cannot divide by 0.");
yield 0.0;
}
yield a / b;
}
default -> 0.0;
};
System.out.printf("Result : %.2f%n", result);
}
System.out.println("Program End");
}
}
Period 07 — 디버거로 흐름 추적
IntelliJ 디버거 한눈에 — 3가지만 알면 끝
- 🔍 디버거(Debugger)의 핵심 가치:
- 코드를 단순히 눈으로 읽거나
System.out.println()으로 일일이 출력해 보지 않고, 프로그램 실행을 원하는 지점에서 일시 정지시켜 메모리에 올라간 변수의 실시간 상태와 코드의 실행 흐름을 완벽하게 추적하는 강력한 도구임.
- 코드를 단순히 눈으로 읽거나
- 🧱 핵심 제어 패널과 4대 단추 (Execution Control):
- 1. Step Over (
F8): 현재 줄의 코드를 실행하고 다음 줄로 이동함. 만약 현재 줄에 메서드(함수)가 있더라도, 그 내부로 들어가지 않고 결과만 툭 받아온 채 다음 줄로 넘어감. - 2. Step Into (
F7): 현재 줄에 있는 메서드 내부로 직접 진입하여 안쪽 로직이 어떻게 돌아가는지 한 단계씩 정밀하게 파헤칠 때 사용함. - 3. Step Out (
Shift + F8):Step Into로 들어간 메서드 내부에서 남은 코드를 마저 다 실행하고, 자신을 호출했던 원래의 바깥쪽 메서드 위치로 탈출할 때 사용함. - 4. Resume Program (
F9): 일시 정지된 상태를 풀고 다음 중단점(Breakpoint)을 만날 때까지 프로그램을 고속으로 쭉 이어서 실행함. 더 이상 중단점이 없다면 프로그램이 정상 종료됨.
- 1. Step Over (
- 🧩 모니터링 창의 이원 구조:
- Frames 창 (좌측): 현재 실행 중인 메서드들의 호출 이력과 계층 구조(Stack Trace)를 시각적으로 보여줌. 어떤 함수를 거쳐 현재 위치에 도달했는지 역추적하기 용집함.
- Variables 창 (우측/하단): 현재 정지된 시점의 메모리에 생성되어 있는 모든 변수와 객체의 실제 값(Data)을 실시간 스냅샷 형태로 나열해 줌. 루프 변수가 변하는 과정이나 배열 내부의 상태를 직관적으로 검증할 수 있음.
- 💡 디버깅 스타트를 위한 필수 트리거:
- 실행을 멈추고 싶은 소스코드 라인 번호 오른쪽 여백을 마우스로 클릭하여 빨간색 동그라미인 중단점(Breakpoint)을 최소 한 개 이상 먼저 매설해야 함.
- 그 후 일반 실행(Run)이 아닌, 벌레 모양 아이콘인 디버그 모드(Debug)로 프로그램을 가동해야 이 디버거 화면이 활성화됨.
디버거 4단계 — 중단점 → Debug → F8 → 관찰
- 🚀 1단계: 중단점(Breakpoint) 설정
- 행동: 소스코드 라인 번호 오른쪽에 있는 여백(Gutter)을 마우스로 클릭하여 빨간색 동그라미를 활성화함.
- 의미: 프로그램 실행 중 메모리와 변수 상태를 “여기서 딱 멈춰줘!”라고 컴퓨터에게 마일스톤을 매설하는 디버깅의 첫 단추임. 중단점이 없으면 디버그 모드로 켜도 그냥 끝까지 실행되어 버림.
- 🪲 2단계: Debug 모드로 실행
- 행동: 일반 실행(Run, 재생 버튼) 대신, 벌레 모양 아이콘인 [Debug] 버튼을 클릭하거나 단축키(
Shift + F9)를 눌러 프로그램을 구동함. - 의미: 디버깅 엔진을 탑재한 채 코드가 가동되며, 1단계에서 지정한 중단점을 만나는 순간 프로그램이 숨을 고르며 일시 정지 상태로 전환되고 하단에 디버거 전용 제어 창이 켜짐.
- 행동: 일반 실행(Run, 재생 버튼) 대신, 벌레 모양 아이콘인 [Debug] 버튼을 클릭하거나 단축키(
- 🏹 3단계: F8 (Step Over)로 한 줄씩 실행
- 행동: 디버거 툴바의
Step Over버튼을 누르거나 키보드F8을 톡톡 누름. - 의미: 프로그램 제어권을 개발자가 쥐고 코드를 딱 한 줄(Line)씩만 전진시키며 실행 흐름을 추적함. 메서드 호출문을 만나도 내부로 빨려 들어가지 않고 결과만 툭 얻은 채 다음 줄로 넘어가므로 전체적인 맥락을 짚기에 가장 최적화된 제어 장치임.
- 행동: 디버거 툴바의
- 👁️ 4단계: 변수(Variables) 실시간 관찰
- 행동: 하단의
Variables모니터링 창을 보거나 소스코드 옆에 연하게 표시되는 인라인 값을 확인하치함. - 의미:
F8을 누를 때마다 메모리 속 변수 데이터가 어떻게 실시간으로 변하고 누적되는지 눈으로 직접 검증하는 단계임. 이를 통해 조건식이 왜true/false가 되었는지, 루프 카운터가 왜 오프바이원(Off-by-one) 에러를 냈는지 논리적 버그의 원인을 완벽하게 포착해 낼 수 있음.
- 행동: 하단의
“버그는 잡는 것이 아니라 발견되는 것이다” — 디버거가 진실을 보여준다. 머릿속으로 추측하지 말고 변수 값을 직접 보라.
흔한 버그 3종 — 입문자 90% 잡기
자바 코딩 입문 단계에서 가장 빈번하게 발생하며, IntelliJ 디버거의 4단계 프로세스(중단점 ➡️ Debug 실행 ➡️ F8 Step Over ➡️ 변수 관찰)를 통해 90% 이상 완벽하게 잡아낼 수 있는 핵심 논리 버그 3가지 유형임.
- ⚠️ 1. 경계값 계산 착오 (Off-By-One 에러)
- 현상: 전체 연산 결과나 반복 횟수가 내가 의도한 정답보다 딱 ‘1’이 모자라거나 ‘1’이 넘치며, 배열을 다룰 때는 프로그램이 터짐.
- 원인: 조건식을 세울 때 이상/이하(
<=,>=)와 초과/미만(<,>) 기호를 혼동했거나, 자바의 인덱스가 0부터 시작한다는 도메인 규칙을 정밀하게 계산하지 못해 발생함. - 디버거 관찰: 루프의 마지막 턴에서 변수 관찰(4단계) 창을 통해 루프 변수
i가 최종 경계값에 도달했을 때, 조건식이 예상대로 탈출하는지 아니면 엉뚱하게 한 번 더 진입하는지 정밀하게 추적하여 검출함.
- ⚠️ 2. 증감식 누락으로 인한 무한 루프 (Infinite Loop)
- 현상: 프로그램이 정상 종료되지 않고 콘솔 창에 똑같은 결과가 멈추지 않으며 무한히 롤링됨.
- 원인:
while문 등에서 조건식을 언젠가false로 만들어 탈출하게 해주는 핵심 장치인 루프 카운터 변수의 증감식(i++)을 본문 내부에서 실수로 누락했기 때문임. - 디버거 관찰: 중단점을 잡고
F8(Step Over)을 아무리 눌러도 루프 변수i의 값이 초기값에서 미동도 하지 않은 채 본문만 뱅글뱅글 도는 상태를 포착하여 원인을 즉시 진단함.
- ⚠️ 3. break; 누락으로 인한 아래로 흘러내림 (Fall-Through 현상)
- 현상:
switch문에서 분명히 변수의 값과 일치하는 단 하나의case블록만 실행되기를 원했으나, 엉뚱한 아래쪽case와default코드까지 줄줄이 연달아 가동됨. - 원인: 전통적인
switch문 구문에서 해당case영역의 실행이 끝난 후 흐름을 끊어주는 제어 키워드인break;를 방심하여 빼먹었기 때문임. (컴퓨터는 조건이 맞은 case를 시작점으로만 인식하고 아래로 직진함) - 디버거 관찰:
F8을 누르며 한 줄씩 코드를 가동할 때, 현재 매칭된case가 끝났음에도 불구하고 튕겨 나가지 않고 다음case라인으로 실행 흐름 화살표가 쑥 흘러내려 가는 모습을 눈으로 직접 확인하여 포착함.
- 현상:
이 3종이 자바 입문자 버그의 90%. 디버거 한 발로 잡힌다 — 두려워 말기.
