본문 바로가기

if와 else로 조건 분기하기

if·else if·else가 실행할 블록 하나를 고르는 규칙과 조건 순서가 결과를 바꾸는 이유를 배우고, 회원가입 입력을 받아들이거나 거절하는 분기를 만듭니다.

앞 글에서는 비교·논리 연산자로 valid, active 같은 boolean 값을 계산했습니다. 하지만 그 값을 출력만 해서는 프로그램이 하는 일은 달라지지 않습니다. 입력이 올바르면 회원을 저장하고, 틀리면 오류 메시지를 보여 주려면 조건에 따라 실행할 문장을 골라야 합니다. 그 일을 하는 것이 if 문입니다.

이 글에서는 if, else, else if가 실행할 블록을 고르는 규칙을 익힙니다. 특히 여러 조건을 이어 쓸 때는 어떤 조건을 먼저 검사하느냐에 따라 같은 입력도 다른 결과가 나온다는 점을 확인하고, 마지막에는 회원가입 입력을 받아들이거나 거절하는 분기를 완성합니다.

if와 else의 모양

if 문은 괄호 안의 조건이 참일 때만 중괄호 안의 문장들을 실행합니다. 중괄호로 묶은 문장들을 블록(block)이라고 부릅니다. 조건이 거짓일 때 실행할 블록이 따로 있다면 else 뒤에 둡니다. 다음 코드는 나이에 따라 두 메시지 중 하나를 출력합니다.

src/AdultCheck.java
public final class AdultCheck {
    public static void main(String[] args) {
        int age = 15;

        if (age >= 18) {
            System.out.println("adult");
        } else {
            System.out.println("underage");
        }
        System.out.println("checked");
    }
}
underage
checked

15는 18보다 작으므로 조건 age >= 18은 거짓입니다. 그래서 첫 블록은 건너뛰고 else 블록이 실행되어 underage가 출력됩니다. 어느 블록을 실행했든 그 블록이 끝나면 if 문 전체를 빠져나와 다음 문장으로 넘어가므로, checked는 언제나 출력됩니다. age를 20으로 바꾸면 adult와 checked가 출력됩니다.

if의 괄호 안에는 반드시 boolean 값이 와야 합니다. 앞 글에서 본 것처럼 Java는 int 같은 다른 타입을 참·거짓으로 바꿔 주지 않으므로, if (count)처럼 정수를 넣으면 컴파일 오류가 납니다. 반대로 boolean 변수는 if (valid)처럼 그대로 조건이 될 수 있습니다.

else if로 세 갈래 이상 나누기

결과가 셋 이상이라면 else 자리에 다시 if를 이어 붙입니다. 이렇게 이어 쓴 if, else if, else를 조건 사슬이라고 부르겠습니다. 조건 사슬은 다음 규칙으로 실행됩니다.

  1. 맨 위 조건부터 차례로 검사합니다.
  2. 처음으로 참인 조건을 만나면 그 블록 하나만 실행하고 사슬 전체를 빠져나갑니다. 그 아래 조건은 검사하지도 않습니다.
  3. 모든 조건이 거짓이면 마지막 else 블록을 실행합니다. else가 없으면 아무 블록도 실행하지 않습니다.

2번 규칙 때문에 한 번 실행할 때 조건 사슬에서 실행되는 블록은 많아야 하나입니다. 이 규칙은 편리하지만, 조건의 순서를 잘못 정하면 엉뚱한 블록이 선택되는 원인이 되기도 합니다.

조건 순서가 결과를 바꾸는 경우

회원 나이는 1세부터 120세까지만 받기로 했다고 합시다. 0 이하는 not-positive, 120 초과는 too-old, 그 사이는 accepted로 판정하려고 다음과 같이 썼습니다. 나이 700을 넣으면 무엇이 출력될지 먼저 예상해 보세요.

lab/WrongBranchOrder.java
public final class WrongBranchOrder {
    public static void main(String[] args) {
        int age = 700;
        String result;

        if (age > 0) {
            result = "accepted";
        } else if (age > 120) {
            result = "too-old";
        } else {
            result = "not-positive";
        }

        System.out.println(result);
    }
}
accepted

700은 첫 조건 age > 0을 만족합니다. 그래서 첫 블록이 실행되고, 사슬은 거기서 끝나 age > 120은 검사조차 되지 않습니다. 컴파일도 실행도 문제없이 끝났지만, 120세를 넘는 나이를 거절해야 한다는 규칙과 다른 결과입니다.

문제는 두 조건의 관계에 있습니다. 120보다 큰 수는 모두 0보다도 크므로, age > 120이 참인 값은 언제나 그보다 먼저 age > 0에서 잡힙니다. 결국 too-old 블록은 어떤 입력으로도 실행될 수 없습니다. 넓은 조건이 좁은 조건을 포함하고 있는데 넓은 조건을 먼저 검사하면, 좁은 조건의 블록은 가려져 버립니다. 아래 그림은 같은 700이 두 가지 조건 순서에서 각각 어떤 검사를 거치는지 보여 줍니다.

700은 첫 조건에서 이미 다른 결과로 갈린다

age 700에서 첫 true로 종료하는 잘못된 조건 순서와 오류 우선의 다른 검사 경로를 대비합니다. 검사 도달·미도달을 단계로 보여 줍니다.

700세 입력의 잘못된 조건 순서와 올바른 조건 순서 비교

FLOWCHART · BRANCH ORDER

700은 첫 조건에서 이미 다른 결과로 갈린다

else-if는 위에서 처음 참인 블록에서 멈춘다. 같은 700도 첫 guard를 어떻게 정의했는지에 따라 accepted 또는 too-old가 된다.

700은 첫 조건에서 이미 다른 결과로 갈린다 같은 age 700이 잘못된 순서에서는 age가 0보다 큰 첫 조건을 통과해 accepted가 되고 최대 나이 검사는 도달하지 않지만, 오류 우선 순서에서는 age 120 초과 조건을 통과해 too-old가 되는 두 실행 trace다. 잘못된 순서 첫 true에서 종료 age = 700 동일 입력 age > 0 ? true · 첫 조건 accepted else-if 평가 종료 PASS age > 120 ? 검사하지 않음 오류 먼저 검사 age = 700 동일 입력 age <= 0 ? false · 다음 검사 FAIL age > 120 ? true · 상한 초과 PASS too-old 700은 거절됨 else-if는 처음 true인 블록 하나만 실행합니다
700은 첫 조건에서 이미 다른 결과로 갈린다 같은 age700에서 정상 선행은 상한을 확인하지 못하고, 오류 선행은700>120으로 거절 A · 잘못된 순서 B · 오류를 먼저 검사 참 거짓 참 age = 700 두 예제의 같은 입력 age > 0 ? 700 > 0은 참 정상으로 잘못 승인 상한을 검사하지 않음 age > 120 검사 앞 분기가 실행되어 여기에는 도달 못함 age = 700 오류부터 걸러냄 age <= 0 ? 700 <= 0은 거짓 age > 120 ? 700 > 120은 참 상한 초과로 거절 정상 분류는 생략

잘못된 순서

  1. age > 0 — PASS
  2. accepted에서 사슬 종료
  3. age > 120 — 검사하지 않음

오류 우선 순서

  1. age <= 0 — FAIL
  2. age > 120 — PASS
  3. too-old로 종료

두 trace의 첫 차이는 결과 노드가 아니라 첫 guard의 범위다.

이 예의 조건

  • 두 분기 코드는 동일 입력 age=700에서 비교합니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2같은 age 700이 잘못된 순서에서는 age가 0보다 큰 첫 조건을 통과해 accepted가 되고 최대 나이 검사에는 도달하지 않지만, 오류 우선 순서에서는 age 120 초과 조건을 통과해 too-old가 되는 두 실행 trace다.
원 도표의 설명과 주석
구분내용
설명같은 age 700이 잘못된 순서에서는 age가 0보다 큰 첫 조건을 통과해 accepted가 되고 최대 나이 검사에는 도달하지 않지만, 오류 우선 순서에서는 age 120 초과 조건을 통과해 too-old가 되는 두 실행 trace다.
전체 읽기 정보700은 첫 조건에서 이미 다른 결과로 갈린다 700세 입력의 잘못된 조건 순서와 올바른 조건 순서 비교 FLOWCHART · BRANCH ORDER 700은 첫 조건에서 이미 다른 결과로 갈린다 else-if는 위에서 처음 참인 블록에서 멈춘다. 같은 700도 첫 guard를 어떻게 정의했는지에 따라 accepted 또는 too-old가 된다. 잘못된 순서 age > 0 — PASS accepted 에서 사슬 종료 age > 120 — 검사하지 않음 오류 우선 순서 age <= 0 — FAIL age > 120 — PASS too-old 로 종료 두 trace의 첫 차이는 결과 노드가 아니라 첫 guard의 범위다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 조건

잘못된 순서는 양수 여부를 먼저 검사합니다.

  • 잘못된 순서: 700>0=true
  • 오류 우선: 700≤0=false
  • else-if는 첫 true 블록에서 선택을 끝냅니다.
도달 여부

잘못된 순서는 accepted를 선택하고 상한 검사에 도달하지 않습니다.

  • 잘못된 결과: accepted
  • 오류 우선 다음 검사: 700>120=true
  • 양수라는 조건이 오류 상한을 가려 버립니다.
결과 구별

오류 우선 순서는 too-old를 선택합니다.

  • 입력: 700 동일
  • 올바른 결과: too-old
  • 좁은 오류 조건을 먼저 두어 허용 범위 밖 입력을 거절합니다.

700은 첫 조건에서 이미 다른 결과로 갈린다

age 700에서 첫 true로 종료하는 잘못된 조건 순서와 오류 우선의 다른 검사 경로를 대비합니다. 검사 도달·미도달을 단계로 보여 줍니다.

700세 입력의 잘못된 조건 순서와 올바른 조건 순서 비교

FLOWCHART · BRANCH ORDER

700은 첫 조건에서 이미 다른 결과로 갈린다

else-if는 위에서 처음 참인 블록에서 멈춘다. 같은 700도 첫 guard를 어떻게 정의했는지에 따라 accepted 또는 too-old가 된다.

700은 첫 조건에서 이미 다른 결과로 갈린다 같은 age 700이 잘못된 순서에서는 age가 0보다 큰 첫 조건을 통과해 accepted가 되고 최대 나이 검사는 도달하지 않지만, 오류 우선 순서에서는 age 120 초과 조건을 통과해 too-old가 되는 두 실행 trace다. 잘못된 순서 첫 true에서 종료 age = 700 동일 입력 age > 0 ? true · 첫 조건 accepted else-if 평가 종료 PASS age > 120 ? 검사하지 않음 오류 먼저 검사 age = 700 동일 입력 age <= 0 ? false · 다음 검사 FAIL age > 120 ? true · 상한 초과 PASS too-old 700은 거절됨 else-if는 처음 true인 블록 하나만 실행합니다
700은 첫 조건에서 이미 다른 결과로 갈린다 같은 age700에서 정상 선행은 상한을 확인하지 못하고, 오류 선행은700>120으로 거절 A · 잘못된 순서 B · 오류를 먼저 검사 참 거짓 참 age = 700 두 예제의 같은 입력 age > 0 ? 700 > 0은 참 정상으로 잘못 승인 상한을 검사하지 않음 age > 120 검사 앞 분기가 실행되어 여기에는 도달 못함 age = 700 오류부터 걸러냄 age <= 0 ? 700 <= 0은 거짓 age > 120 ? 700 > 120은 참 상한 초과로 거절 정상 분류는 생략

잘못된 순서

  1. age > 0 — PASS
  2. accepted에서 사슬 종료
  3. age > 120 — 검사하지 않음

오류 우선 순서

  1. age <= 0 — FAIL
  2. age > 120 — PASS
  3. too-old로 종료

두 trace의 첫 차이는 결과 노드가 아니라 첫 guard의 범위다.

이 예의 조건

  • 두 분기 코드는 동일 입력 age=700에서 비교합니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2같은 age 700이 잘못된 순서에서는 age가 0보다 큰 첫 조건을 통과해 accepted가 되고 최대 나이 검사에는 도달하지 않지만, 오류 우선 순서에서는 age 120 초과 조건을 통과해 too-old가 되는 두 실행 trace다.
원 도표의 설명과 주석
구분내용
설명같은 age 700이 잘못된 순서에서는 age가 0보다 큰 첫 조건을 통과해 accepted가 되고 최대 나이 검사에는 도달하지 않지만, 오류 우선 순서에서는 age 120 초과 조건을 통과해 too-old가 되는 두 실행 trace다.
전체 읽기 정보700은 첫 조건에서 이미 다른 결과로 갈린다 700세 입력의 잘못된 조건 순서와 올바른 조건 순서 비교 FLOWCHART · BRANCH ORDER 700은 첫 조건에서 이미 다른 결과로 갈린다 else-if는 위에서 처음 참인 블록에서 멈춘다. 같은 700도 첫 guard를 어떻게 정의했는지에 따라 accepted 또는 too-old가 된다. 잘못된 순서 age > 0 — PASS accepted 에서 사슬 종료 age > 120 — 검사하지 않음 오류 우선 순서 age <= 0 — FAIL age > 120 — PASS too-old 로 종료 두 trace의 첫 차이는 결과 노드가 아니라 첫 guard의 범위다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 조건

잘못된 순서는 양수 여부를 먼저 검사합니다.

  • 잘못된 순서: 700>0=true
  • 오류 우선: 700≤0=false
  • else-if는 첫 true 블록에서 선택을 끝냅니다.
도달 여부

잘못된 순서는 accepted를 선택하고 상한 검사에 도달하지 않습니다.

  • 잘못된 결과: accepted
  • 오류 우선 다음 검사: 700>120=true
  • 양수라는 조건이 오류 상한을 가려 버립니다.
결과 구별

오류 우선 순서는 too-old를 선택합니다.

  • 입력: 700 동일
  • 올바른 결과: too-old
  • 좁은 오류 조건을 먼저 두어 허용 범위 밖 입력을 거절합니다.

고치는 방법은 거절할 조건을 먼저 검사하는 것입니다. 잘못된 값을 위에서 걸러 내고 나면, 마지막 else에는 올바른 값만 남습니다.

src/AgeValidation.java
public final class AgeValidation {
    public static void main(String[] args) {
        int age = 700;
        String result;

        if (age <= 0) {
            result = "not-positive";
        } else if (age > 120) {
            result = "too-old";
        } else {
            result = "accepted";
        }

        System.out.println(result);
    }
}
too-old

700은 age <= 0에서 거짓이 되고, 다음 age > 120에서 참이 되어 too-old가 출력됩니다. 이 사슬은 정수 전체를 겹치지 않는 세 구간으로 나눕니다.

구간실행되는 블록그 블록에 도달하는 조건
0 이하not-positiveage <= 0이 참
121 이상too-oldage <= 0이 거짓이고 age > 120이 참
1–120accepted두 조건이 모두 거짓

표의 셋째 열을 보면 else if의 조건이 실제로 무엇을 뜻하는지 알 수 있습니다. else if (age > 120)은 코드에 쓰인 대로의 age > 120이 아니라, '앞 조건이 거짓이었고 그러면서 age > 120'을 뜻합니다. 마지막 else도 '위의 조건이 모두 거짓'일 때이므로, 1–120이라는 범위를 코드에 직접 쓰지 않아도 그 범위만 받습니다.

두 프로그램 모두 String result;처럼 처음 값 없이 변수를 선언하고, 각 블록에서 값을 대입했습니다. 이렇게 해도 되는 것은 마지막 else가 있어 어느 경로로 가든 result에 값이 들어가기 때문입니다. else 블록을 지우면 두 조건이 모두 거짓일 때 result가 비게 되므로, 컴파일러가 println(result) 줄에서 variable result might not have been initialized 오류를 냅니다. 컴파일러는 이처럼 변수를 읽기 전에 모든 경로에서 값이 대입되었는지 검사합니다.

더 깊이: 컴파일러는 실행될 수 없는 분기를 왜 알려 주지 않을까

WrongBranchOrder의 too-old 블록은 절대 실행되지 않지만 javac는 아무 경고도 하지 않습니다. 컴파일러가 실행될 수 없는 코드를 찾는 규칙은 Java 언어 명세에 정해져 있는데, 이 규칙은 변수가 가질 수 있는 값의 범위를 추론하지 않기 때문입니다. age > 0이 거짓이라면 age > 120도 거짓이라는 것은 사람에게는 당연하지만, 명세가 정한 분석의 범위 밖입니다.

게다가 if는 의도적으로 예외 취급을 받습니다. while (false)의 본문은 '실행될 수 없는 문장'이라며 컴파일 오류가 나지만, if (false)의 블록은 오류 없이 컴파일됩니다. 상수 하나를 true나 false로 바꿔 디버깅용 코드를 켜고 끄는 용도로 if를 쓸 수 있게 하려고 명세가 일부러 허용한 것입니다. IDE의 정적 분석 기능이 '항상 거짓인 조건'을 표시해 주는 경우도 있지만, 조건의 순서와 범위가 맞는지는 결국 직접 확인해야 합니다. 경계값으로 조건문 점검하기에서 그 방법을 연습합니다.

독립된 if 여러 개와 else if 사슬

조건 사슬은 많아야 블록 하나를 고르지만, 여러 결과가 동시에 필요할 때도 있습니다. 성인이면 adult 표시를, 65세 이상이면 senior 표시를 각각 붙이는 경우를 보겠습니다. 90세 회원에게는 두 표시가 모두 붙어야 합니다.

src/IndependentConditions.java
public final class IndependentConditions {
    public static void main(String[] args) {
        int age = 90;

        if (age >= 18) {
            System.out.println("adult");
        }
        if (age >= 65) {
            System.out.println("senior");
        }
    }
}
adult
senior

두 if는 else로 이어져 있지 않은 별개의 문장이므로 각자 자기 조건을 검사합니다. 90은 두 조건을 모두 만족하므로 두 줄이 다 출력됩니다. 이처럼 해당하는 것을 모두 실행해야 한다면 독립된 if를 씁니다.

반대로 회원을 underage, adult, senior 가운데 정확히 하나로 분류하려면 else if 사슬을 씁니다.

if (age < 18) {
    label = "underage";
} else if (age < 65) {
    label = "adult";
} else {
    label = "senior";
}

두 번째 조건 age < 65에 도달했다면 첫 조건 age < 18이 이미 거짓이었다는 뜻이고, 따라서 age >= 18이 성립합니다. 그래서 age >= 18 && age < 65라고 하한을 다시 쓰지 않아도 18–64만 이 블록에 들어옵니다. 같은 이유로 마지막 else는 65 이상을 받습니다. 독립된 if로 바꾸면 이 전제가 사라지므로, 90세는 adult와 senior 두 블록을 모두 실행하게 됩니다.

판단 기준은 간단합니다. 결과가 여러 개 동시에 나올 수 있으면 독립된 if를, 결과가 하나여야 하면 else if 사슬을 씁니다.

중괄호를 생략하면 생기는 일

블록에 문장이 하나뿐이면 중괄호를 생략할 수 있습니다. 하지만 그때 조건에 속하는 것은 바로 다음 문장 하나뿐이고, 들여쓰기는 아무 의미가 없습니다. 이 때문에 코드가 보이는 모양과 실제로 실행되는 구조가 달라질 수 있습니다. 다음 코드는 이메일이 없으면 거절 메시지를 출력하려고 쓴 것처럼 보입니다.

lab/MisleadingIndentation.java
public final class MisleadingIndentation {
    public static void main(String[] args) {
        boolean emailPresent = false;
        int age = 45;

        if (emailPresent)
            if (age > 0)
                System.out.println("accepted");
        else
            System.out.println("age rejected");

        System.out.println("done");
    }
}
done

else는 짝이 없는 if 가운데 가장 가까운 것에 붙습니다. 여기서는 바깥 if (emailPresent)가 아니라 안쪽 if (age > 0)이 그 짝입니다. 그러니 실제 구조는 '이메일이 있으면, 나이에 따라 accepted 또는 age rejected를 출력한다'입니다. 이메일이 없으면 안쪽 if와 else 전체가 통째로 건너뛰어지므로, 거절 메시지 없이 done만 출력됩니다. emailPresent가 true이고 age가 -5일 때 비로소 age rejected가 출력됩니다.

중괄호가 없으면 나중에 문장을 추가할 때도 같은 일이 생깁니다. 다음은 원래 if (age > 0) 아래에 출력 한 줄만 있던 코드에 저장 개수를 세는 줄을 추가한 경우입니다.

int age = -5;
int savedCount = 0;
if (age > 0)
    System.out.println("accepted");
    savedCount++;
System.out.println("savedCount=" + savedCount);

들여쓰기만 보면 savedCount++도 조건 안에 있는 것 같지만, 조건에 속하는 것은 println 한 문장뿐입니다. 그래서 age가 -5인데도 savedCount++가 실행되어 savedCount=1이 출력됩니다.

중괄호를 항상 쓰면 블록에 무엇이 속하는지가 코드에 그대로 드러나므로 이런 문제가 생기지 않습니다. 앞의 이메일·나이 검사를 의도대로 다시 쓰면 다음과 같습니다.

if (!emailPresent) {
    System.out.println("email rejected");
} else if (age <= 0) {
    System.out.println("age rejected");
} else {
    System.out.println("accepted");
}

이 장의 조건문은 문장이 하나뿐이어도 모두 중괄호를 씁니다. 뒤의 장에서는 if (value == null) continue;처럼 아주 짧은 검사를 한 줄로 쓰기도 하는데, 이때도 조건과 문장을 같은 줄에 두어 그 문장 하나만 조건에 속한다는 것이 드러나게 합니다.

회원가입 입력을 받아들이거나 거절하기

이제 지금까지의 규칙을 모아 회원가입 판정을 만들어 보겠습니다. 이메일이 비어 있거나 나이가 1–120을 벗어나면 이유를 출력하고, 통과한 입력만 나이대 라벨을 붙여 saved=로 시작하는 줄로 출력합니다. 분기 자체에 집중하려고 값은 코드에 직접 적었습니다. 실행 인자로 받은 문자열을 숫자로 바꾸다 실패하는 경우는 3장의 입력 검증과 재입력 반복에서 다룹니다.

src/SignupDecision.java
public class SignupDecision {
    public static void main(String[] args) {
        String email = "[email protected]";
        int age = 45;

        if (email.isBlank()) {
            System.out.println("email is required");
        } else if (age <= 0) {
            System.out.println("age must be positive");
        } else if (age > 120) {
            System.out.println("age must be at most 120");
        } else {
            String label;
            if (age < 18) {
                label = "underage";
            } else if (age < 65) {
                label = "adult";
            } else {
                label = "senior";
            }
            System.out.println("saved=" + email + ":" + age + ":" + label);
        }
    }
}
실행 결과

이 프로그램에는 조건 사슬이 두 개 있습니다. 바깥 사슬은 거절할 이유를 위에서부터 하나씩 검사하고, 이유가 하나라도 있으면 그 메시지만 출력하고 끝냅니다. 이렇게 잘못된 입력을 먼저 걸러 내는 조건을 앞 글의 단축 평가에서처럼 가드라고 부릅니다. 세 가드를 모두 통과해야 마지막 else에 들어가는데, 그 안에 있는 안쪽 사슬은 이미 1–120으로 좁혀진 나이만 다루므로 범위 밖의 값을 신경 쓸 필요가 없습니다. 45세는 세 가드를 통과한 뒤 안쪽 사슬의 age < 65에서 참이 되어 adult로 분류됩니다.

saved=로 시작하는 줄은 저장에 성공했다고 가정하고 출력하는 문자열일 뿐이고, 아직 회원을 실제로 어딘가에 보관하지는 않습니다. 여러 회원을 보관하는 기능은 3장에서 배열을 배운 뒤 만듭니다. 아래 그림에서 입력이 가드를 차례로 통과해 라벨 하나에 도달하는 과정을 확인해 보세요.

오류 가드를 통과한 나이만 하나의 라벨로 출력된다

이메일/나이 가드 후 남은 범위에서 underage/adult/senior를 선택하는 실제 분기입니다. 현재 판정과 이후 미도달 조건을 단계로 구분할 수 있습니다.

회원가입 오류 guard와 배타적 나이 분류 흐름

FLOWCHART · GUARD CLAUSES

오류 가드를 통과한 나이만 하나의 라벨로 출력된다

이메일과 나이 오류를 먼저 종료하면 1–120만 남는다. 정상 범위는 겹치지 않는 세 구간 중 하나로 분류하여 saved 문자열 한 줄을 출력합니다.

오류 가드를 통과한 나이만 하나의 라벨로 출력된다 이메일/나이 세 오류 guard는 참이면 출력 후 종료,거짓만 다음 검사; 정상1–120은underage/adult/senior중하나→saved출력(저장아님) email · age 입력 정수 변환 성공 전제 이메일 공백? email.isBlank() 참 오류 출력 · 종료 email is required 거짓 나이 0 이하? age <= 0 참 오류 출력 · 종료 age must be positive 거짓 나이 120 초과? age > 120 참 오류 출력 · 종료 age must be at most 120 거짓 age < 18 ? 남은 범위 1–120 참 underage 1–17 거짓 age < 65 ? 남은 범위 18–120 참 adult 18–64 거짓 senior 65–120 saved 문자열 출력 email:age:label 아직 저장소 기록은 없음
오류 가드를 통과한 나이만 하나의 라벨로 출력된다 이메일/나이 세 오류 guard는 참이면 출력 후 종료,거짓만 다음 검사; 정상1–120은underage/adult/senior중하나→saved출력(저장아님) email · age 입력 정수 변환 성공 전제 참 거짓 이메일 공백? email.isBlank() 오류 출력 · 종료 email is required 참 거짓 나이 0 이하? age <= 0 오류 출력 · 종료 age must be positive 참 거짓 나이 120 초과? age > 120 오류 출력 · 종료 age must be at most 120 age < 18 ? 남은 범위 1–120 참 underage 1–17 거짓 age < 65 ? 남은 범위 18–120 참 adult 18–64 거짓 senior 65–120 saved 문자열 출력 email:age:label 아직 저장소 기록은 없음

1 · 위에서부터 오류를 검사합니다

  1. 이메일이 비어 있는가?

    email.isBlank()

    true → email is required 출력 후 종료

    false → 다음 나이 검사

  2. 나이가 0 이하인가?

    age <= 0

    true → age must be positive 출력 후 종료

    false → 상한 검사

  3. 나이가 120을 초과하는가?

    age > 120

    true → age must be at most 120 출력 후 종료

    false → 정상 범위 1–120으로 진행

2 · 남은 범위에서 라벨 하나를 고릅니다

1–17

underage

18–64

adult

65–120

senior

else-if는 처음 true인 구간 하나만 실행합니다. 정상 범위에는 빈틈과 중복이 없습니다.

3 · saved 결과 한 줄을 출력합니다

saved=email:age:label

본문 코드는 System.out.println으로 결과를 보여줍니다. 실제 저장소에 회원을 기록하는 기능은 아직 없습니다.

세 오류 guard가 모두 false라는 사실이 정상 범위 1–120의 전제가 된다.

이 예의 조건

  • 정수 변환 성공을 전제합니다. saved는 콘솔 문자열이며 저장소 기록이 아닙니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2이메일/나이 세 오류 guard는 참이면 출력 후 종료, 거짓만 다음 검사; 정상 1–120은 underage/adult/senior 중 하나→saved 출력(저장 아님)
원 도표의 설명과 주석
구분내용
설명이메일/나이 세 오류 guard는 참이면 출력 후 종료, 거짓만 다음 검사; 정상 1–120은 underage/adult/senior 중 하나→saved 출력(저장 아님)
전체 읽기 정보오류 가드를 통과한 나이만 하나의 라벨로 출력된다 회원가입 오류 guard와 배타적 나이 분류 흐름 FLOWCHART · GUARD CLAUSES 오류 가드를 통과한 나이만 하나의 라벨로 출력된다 이메일과 나이 오류를 먼저 종료하면 1–120만 남는다. 정상 범위는 겹치지 않는 세 구간 중 하나로 분류하여 saved 문자열 한 줄을 출력합니다. 1 · 위에서부터 오류를 검사합니다 이메일이 비어 있는가? email.isBlank() true → email is required 출력 후 종료 false → 다음 나이 검사 나이가 0 이하인가? age <= 0 true → age must be positive 출력 후 종료 false → 상한 검사 나이가 120을 초과하는가? age > 120 true → age must be at most 120 출력 후 종료 false → 정상 범위 1–120으로 진행 2 · 남은 범위에서 라벨 하나를 고릅니다 1–17 underage 18–64 adult 65–120 senior else-if는 처음 true인 구간 하나만 실행합니다. 정상 범위에는 빈틈과 중복이 없습니다. 3 · saved 결과 한 줄을 출력합니다 saved=email:age:label 본문 코드는 System.out.println으로 결과를 보여줍니다. 실제 저장소에 회원을 기록하는 기능은 아직 없습니다. 세 오류 guard가 모두 false라는 사실이 정상 범위 1–120 의 전제가 된다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
오류 가드

공백 email, age≤0, age>120을 차례로 검사합니다.

  • 참: 각 오류 출력 후 종료
  • 거짓: 다음 검사로 진행
  • 오류가 없는 입력만 분류에 도달합니다.
라벨 선택

남은 age 1–120에서 두 경계를 순서대로 검사합니다.

  • 1–17: underage
  • 18–64 / 65–120: adult / senior
  • 한 입력은 세 라벨 중 하나만 선택합니다.
saved 출력

email:age:label 형태를 출력합니다.

  • 산출물: saved 문자열
  • 저장소: 기록 없음
  • 로그 이름만으로 영속 저장을 추론하지 않습니다.

오류 가드를 통과한 나이만 하나의 라벨로 출력된다

이메일/나이 가드 후 남은 범위에서 underage/adult/senior를 선택하는 실제 분기입니다. 현재 판정과 이후 미도달 조건을 단계로 구분할 수 있습니다.

회원가입 오류 guard와 배타적 나이 분류 흐름

FLOWCHART · GUARD CLAUSES

오류 가드를 통과한 나이만 하나의 라벨로 출력된다

이메일과 나이 오류를 먼저 종료하면 1–120만 남는다. 정상 범위는 겹치지 않는 세 구간 중 하나로 분류하여 saved 문자열 한 줄을 출력합니다.

오류 가드를 통과한 나이만 하나의 라벨로 출력된다 이메일/나이 세 오류 guard는 참이면 출력 후 종료,거짓만 다음 검사; 정상1–120은underage/adult/senior중하나→saved출력(저장아님) email · age 입력 정수 변환 성공 전제 이메일 공백? email.isBlank() 참 오류 출력 · 종료 email is required 거짓 나이 0 이하? age <= 0 참 오류 출력 · 종료 age must be positive 거짓 나이 120 초과? age > 120 참 오류 출력 · 종료 age must be at most 120 거짓 age < 18 ? 남은 범위 1–120 참 underage 1–17 거짓 age < 65 ? 남은 범위 18–120 참 adult 18–64 거짓 senior 65–120 saved 문자열 출력 email:age:label 아직 저장소 기록은 없음
오류 가드를 통과한 나이만 하나의 라벨로 출력된다 이메일/나이 세 오류 guard는 참이면 출력 후 종료,거짓만 다음 검사; 정상1–120은underage/adult/senior중하나→saved출력(저장아님) email · age 입력 정수 변환 성공 전제 참 거짓 이메일 공백? email.isBlank() 오류 출력 · 종료 email is required 참 거짓 나이 0 이하? age <= 0 오류 출력 · 종료 age must be positive 참 거짓 나이 120 초과? age > 120 오류 출력 · 종료 age must be at most 120 age < 18 ? 남은 범위 1–120 참 underage 1–17 거짓 age < 65 ? 남은 범위 18–120 참 adult 18–64 거짓 senior 65–120 saved 문자열 출력 email:age:label 아직 저장소 기록은 없음

1 · 위에서부터 오류를 검사합니다

  1. 이메일이 비어 있는가?

    email.isBlank()

    true → email is required 출력 후 종료

    false → 다음 나이 검사

  2. 나이가 0 이하인가?

    age <= 0

    true → age must be positive 출력 후 종료

    false → 상한 검사

  3. 나이가 120을 초과하는가?

    age > 120

    true → age must be at most 120 출력 후 종료

    false → 정상 범위 1–120으로 진행

2 · 남은 범위에서 라벨 하나를 고릅니다

1–17

underage

18–64

adult

65–120

senior

else-if는 처음 true인 구간 하나만 실행합니다. 정상 범위에는 빈틈과 중복이 없습니다.

3 · saved 결과 한 줄을 출력합니다

saved=email:age:label

본문 코드는 System.out.println으로 결과를 보여줍니다. 실제 저장소에 회원을 기록하는 기능은 아직 없습니다.

세 오류 guard가 모두 false라는 사실이 정상 범위 1–120의 전제가 된다.

이 예의 조건

  • 정수 변환 성공을 전제합니다. saved는 콘솔 문자열이며 저장소 기록이 아닙니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2이메일/나이 세 오류 guard는 참이면 출력 후 종료, 거짓만 다음 검사; 정상 1–120은 underage/adult/senior 중 하나→saved 출력(저장 아님)
원 도표의 설명과 주석
구분내용
설명이메일/나이 세 오류 guard는 참이면 출력 후 종료, 거짓만 다음 검사; 정상 1–120은 underage/adult/senior 중 하나→saved 출력(저장 아님)
전체 읽기 정보오류 가드를 통과한 나이만 하나의 라벨로 출력된다 회원가입 오류 guard와 배타적 나이 분류 흐름 FLOWCHART · GUARD CLAUSES 오류 가드를 통과한 나이만 하나의 라벨로 출력된다 이메일과 나이 오류를 먼저 종료하면 1–120만 남는다. 정상 범위는 겹치지 않는 세 구간 중 하나로 분류하여 saved 문자열 한 줄을 출력합니다. 1 · 위에서부터 오류를 검사합니다 이메일이 비어 있는가? email.isBlank() true → email is required 출력 후 종료 false → 다음 나이 검사 나이가 0 이하인가? age <= 0 true → age must be positive 출력 후 종료 false → 상한 검사 나이가 120을 초과하는가? age > 120 true → age must be at most 120 출력 후 종료 false → 정상 범위 1–120으로 진행 2 · 남은 범위에서 라벨 하나를 고릅니다 1–17 underage 18–64 adult 65–120 senior else-if는 처음 true인 구간 하나만 실행합니다. 정상 범위에는 빈틈과 중복이 없습니다. 3 · saved 결과 한 줄을 출력합니다 saved=email:age:label 본문 코드는 System.out.println으로 결과를 보여줍니다. 실제 저장소에 회원을 기록하는 기능은 아직 없습니다. 세 오류 guard가 모두 false라는 사실이 정상 범위 1–120 의 전제가 된다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
오류 가드

공백 email, age≤0, age>120을 차례로 검사합니다.

  • 참: 각 오류 출력 후 종료
  • 거짓: 다음 검사로 진행
  • 오류가 없는 입력만 분류에 도달합니다.
라벨 선택

남은 age 1–120에서 두 경계를 순서대로 검사합니다.

  • 1–17: underage
  • 18–64 / 65–120: adult / senior
  • 한 입력은 세 라벨 중 하나만 선택합니다.
saved 출력

email:age:label 형태를 출력합니다.

  • 산출물: saved 문자열
  • 저장소: 기록 없음
  • 로그 이름만으로 영속 저장을 추론하지 않습니다.

입력마다 어느 블록으로 가는지 표로 확인하기

email과 age를 바꿔 가며 다시 실행하면, 입력마다 정확히 한 블록만 실행되는 것을 확인할 수 있습니다. 결과가 바뀌는 지점의 값들을 골라 표로 정리하면 다음과 같습니다. 표의 '없음'은 그 사슬의 조건이 모두 거짓이어서 else 블록이 실행되었다는 뜻이고, '-'는 그 사슬에 도달하지 않았다는 뜻입니다.

입력바깥 사슬에서 처음 참인 조건안쪽 사슬에서 처음 참인 조건결과
이메일이 공백email.isBlank()-email is required 출력
0세age <= 0-age must be positive 출력
1세없음age < 18underage로 분류해 saved 출력
17세없음age < 18underage로 분류해 saved 출력
18세없음age < 65adult로 분류해 saved 출력
64세없음age < 65adult로 분류해 saved 출력
65세없음없음senior로 분류해 saved 출력
120세없음없음senior로 분류해 saved 출력
121세age > 120-age must be at most 120 출력

표에 고른 나이는 모두 결과가 바뀌는 기준의 바로 양옆입니다. 0과 1, 17과 18, 64와 65, 120과 121을 한 쌍씩 실행해 보면 <와 <=를 바꿔 쓰는 실수가 바로 드러납니다. 예를 들어 age < 65를 age <= 65로 잘못 쓰면 65세가 adult로 분류되는데, 64세나 70세만 실행해 봐서는 이 실수를 찾을 수 없습니다. 이렇게 기준 근처의 값으로 조건을 점검하는 방법은 경계값으로 조건문 점검하기에서 본격적으로 다룹니다.

정리

  • if는 조건이 참일 때 블록을 실행하고, else는 거짓일 때의 블록을 정합니다. 조건은 반드시 boolean이어야 합니다.
  • if, else if, else로 이은 조건 사슬은 위에서부터 검사해 처음 참인 블록 하나만 실행합니다. 그래서 각 else if에는 '앞 조건이 모두 거짓'이라는 전제가 붙습니다.
  • 넓은 조건을 먼저 검사하면 그 안에 포함된 좁은 조건의 블록은 실행될 수 없습니다. 거절할 조건을 먼저 걸러 내면 마지막 else에 올바른 값만 남습니다.
  • 결과가 여러 개 동시에 필요하면 독립된 if를, 정확히 하나여야 하면 else if 사슬을 씁니다.
  • else는 짝이 없는 가장 가까운 if에 붙고, 중괄호가 없으면 조건에 속하는 것은 다음 문장 하나뿐입니다. 블록에는 항상 중괄호를 씁니다.

다음 글에서는 한 값을 여러 후보와 비교할 때 쓰는 switch와, 두 값 중 하나를 고르는 삼항 연산자를 배웁니다.

연습 문제

문제 1: 로그인 실패 횟수 분류하기

실행 인자로 받은 로그인 실패 횟수를 다음 라벨 가운데 하나로 분류해 출력하는 LoginAttemptClassifier를 작성하세요. 0, 2, 5, 6을 각각 넣어 실행해 보고, 조건 순서가 의도대로인지 확인합니다.

  • 음수: invalid
  • 0: clean
  • 1–2: allowed
  • 3–5: warning
  • 6 이상: locked
해설 보기
src/LoginAttemptClassifier.java
public final class LoginAttemptClassifier {
    public static void main(String[] args) {
        int failedLoginCount = Integer.parseInt(args[0]);
        String label;

        if (failedLoginCount < 0) {
            label = "invalid";
        } else if (failedLoginCount == 0) {
            label = "clean";
        } else if (failedLoginCount <= 2) {
            label = "allowed";
        } else if (failedLoginCount <= 5) {
            label = "warning";
        } else {
            label = "locked";
        }

        System.out.println(label);
    }
}
> java -cp out LoginAttemptClassifier 0
clean
> java -cp out LoginAttemptClassifier 2
allowed
> java -cp out LoginAttemptClassifier 5
warning
> java -cp out LoginAttemptClassifier 6
locked

작은 값부터 위에서 걸러 내기 때문에 각 조건은 앞 조건이 남긴 값만 받습니다. failedLoginCount <= 2에 도달한 값은 이미 음수도 0도 아니므로 실제로는 1–2를 뜻하고, failedLoginCount <= 5는 같은 이유로 3–5를 뜻합니다. 만약 failedLoginCount >= 0 같은 넓은 조건을 맨 앞에 두면 0 이상인 값이 모두 거기서 잡혀 clean부터 locked까지의 블록에 도달할 수 없게 됩니다.

문제 2: 실행될 수 없는 블록 찾기

다음 코드에서 score가 95일 때 무엇이 출력되나요? excellent가 출력되는 score 값이 하나라도 있는지 설명하고, 90점 이상이면 excellent, 60점 이상 90점 미만이면 pass가 출력되도록 고치세요.

if (score >= 60) {
    System.out.println("pass");
} else if (score >= 90) {
    System.out.println("excellent");
}
해설 보기

95는 첫 조건 score >= 60을 만족하므로 pass가 출력되고 사슬이 끝납니다. 90 이상인 값은 모두 60 이상이기도 해서 언제나 첫 조건에서 잡히므로, excellent가 출력되는 값은 하나도 없습니다. 좁은 조건을 먼저 검사하도록 순서를 바꾸면 됩니다.

if (score >= 90) {
    System.out.println("excellent");
} else if (score >= 60) {
    System.out.println("pass");
}

이제 두 번째 조건에는 '90 미만'이라는 전제가 붙으므로 60–89만 pass가 됩니다. 60 미만이면 어느 조건도 참이 아니고 else도 없으므로 아무것도 출력되지 않습니다.

문제 3: 들여쓰기에 속지 않기

본문의 MisleadingIndentation에서 emailPresent를 true로, age를 -5로 바꾸면 무엇이 출력되나요? 그 이유도 설명하세요.

해설 보기

age rejected와 done이 차례로 출력됩니다. emailPresent가 참이므로 바깥 if의 유일한 문장인 안쪽 if-else가 실행됩니다. -5는 age > 0을 만족하지 않으므로 안쪽 if의 짝인 else가 실행되어 age rejected가 출력되고, 그다음 if와 상관없는 done이 출력됩니다. 들여쓰기는 바깥 if의 else처럼 보이게 했지만, 실행 구조는 else를 가장 가까운 if에 붙이는 규칙대로 정해집니다.