안동민 개발노트

본문 시작

switch 식과 삼항 연산

닫힌 선택지를 switch 문·switch 식으로 분리하고 두 값 중 하나를 고르는 경우에만 삼항 연산자를 사용합니다.

if는 범위와 서로 다른 조건식을 다루기 좋지만 하나의 명령 문자열이나 메뉴 번호를 반복 비교하면 대상이 묻힙니다.

switch는 같은 값의 여러 후보를 한곳에 모읍니다.

Java의 전통적인 콜론형 switch 문은 break가 없으면 다음 case로 흐르고, 화살표 switch 식은 분기마다 결과 값을 만들 수 있습니다.

콜론형 switch의 fall-through와 break 실행 범위 비교

FLOWCHART · FALL-THROUGH

case 1 진입 뒤 break 유무가 실행 범위를 바꾼다

콜론형 switch는 일치한 case에서 진입하지만 다음 case label에서 자동으로 멈추지 않는다. 같은 menu 1도 break 유무에 따라 stdout 범위가 달라진다.

case 1 진입 뒤 break 유무가 실행 범위를 바꾼다 동일한 menu 1이 break 없는 콜론형 switch에서는 signup, members, quit, unknown을 차례로 출력하고, case 1의 break가 있으면 signup 뒤 switch 다음 문장으로 이동하는 두 실행 trace다. NO BREAK normal completion continues menu = 1 · case 1 진입 일치 지점 println("signup") stdout 1 case 2 · "members" stdout 2 case 0 · "quit" stdout 3 default · "unknown" stdout 4 after switch 블록 끝에서 이동 BREAK IN CASE 1 same input · isolated execution menu = 1 · case 1 진입 일치 지점 println("signup"); break; stdout = signup break after switch members · quit · unknown 미실행 CASE LABELS SELECT ENTRY · BREAK SELECTS EXIT

break 없음 · menu = 1

  1. signup
  2. members
  3. quit
  4. unknown
  5. after switch

case 1에 break · menu = 1

  1. signup
  2. break로 switch 종료
  3. after switch

case 2, case 0, default는 실행하지 않습니다.

case label은 진입점을 고르고, break는 가장 가까운 switch의 실행 범위를 끝낸다.


switch 관통 실행

lab/SwitchFallThrough.java
public final class SwitchFallThrough {
    public static void main(String[] args) {
        int menu = 1;

        switch (menu) {
            case 1:
                System.out.println("signup");
            case 2:
                System.out.println("members");
            case 0:
                System.out.println("quit");
            default:
                System.out.println("unknown");
        }
    }
}
signup
members
quit
unknown

case 1에서 시작한 뒤 switch 블록 끝까지 순서대로 실행됐습니다.

case는 다음 case에 도착했다고 자동으로 멈추지 않습니다.

의도적인 fall-through로 여러 후보가 같은 코드를 공유할 수도 있지만, 누락인지 의도인지 구분하기 어렵습니다.

콜론형 문을 유지한다면 각 경로에서 break로 switch를 빠져나옵니다.

src/SwitchStatement.java
public final class SwitchStatement {
    public static void main(String[] args) {
        int menu = 1;

        switch (menu) {
            case 1:
                System.out.println("signup");
                break;
            case 2:
                System.out.println("members");
                break;
            case 0:
                System.out.println("quit");
                break;
            default:
                System.out.println("unknown");
        }
    }
}

break는 가장 가까운 switch 문을 끝내고 그 다음 문장으로 이동합니다.

반복문의 break와 이름은 같지만 빠져나가는 대상은 현재 감싸고 있는 switch입니다.


switch 표현식

src/CommandLabel.java
public final class CommandLabel {
    public static void main(String[] args) {
        String command = args.length == 0 ? "help" : args[0];

        String label = switch (command) {
            case "env" -> "toolchain report";
            case "runtime" -> "runtime report";
            case "paths" -> "workspace report";
            case "signup" -> "register a member";
            case "help" -> "show usage";
            default -> "unknown command";
        };

        System.out.println(label);
    }
}
java -cp out CommandLabel signup
register a member

switch (command) 전체가 String 값을 만들고 마지막 세미콜론에서 대입 문장이 끝납니다.

화살표 오른쪽 분기는 다음 case로 흘러가지 않습니다.

label을 먼저 빈 값으로 초기화한 뒤 각 case에서 다시 대입할 필요도 없습니다.

여러 문장을 실행한 뒤 값을 내놓으려면 블록과 yield를 사용합니다.

src/SwitchYield.java
public final class SwitchYield {
    public static void main(String[] args) {
        String role = "member";

        String description = switch (role) {
            case "guest" -> "read only";
            case "member" -> {
                System.out.println("role=" + role);
                yield "write posts";
            }
            case "admin" -> "manage members";
            default -> "unknown role";
        };

        System.out.println(description);
    }
}
role=member
write posts

yield는 switch 식의 해당 분기 결과를 제공합니다.

메서드를 끝내는 return과 다릅니다.

switch 식에서 정상 완료하는 모든 분기는 호환되는 타입의 결과를 제공해야 하며, 값을 만들지 않는 분기는 예외로 끝나야 합니다.

switch expression의 command case와 String 결과 값 매핑

TREE · SWITCH VALUE MAP

각 case는 String 값 하나를 골라 label을 초기화한다

switch 식은 같은 command selector 아래 후보를 모으고, 일치한 화살표 case 하나의 String 값으로 label을 초기화한다. 블록 arm도 yield로 같은 계약에 참여한다.

각 case는 String 값 하나를 골라 label을 초기화한다 switch command가 env, runtime, paths, signup, help와 default를 구분하고 각 화살표 case가 정확한 String 값 하나를 선택하며, 여러 문장의 block arm은 yield로 결과를 제공할 수 있음을 보여 준다. switch(command) String selector 일치한 화살표 case fall-through 없이 값 하나 선택 default unknown command env toolchain report runtime runtime report paths workspace report signup register a member help show usage 블록 arm도 같은 결과 계약 println("role=" + role); yield "write posts"; 여러 문장을 실행한 뒤 yield가 선택된 분기의 값을 제공한다. SELECTED CASE VALUE → String label · ONE ARM · NO FALL-THROUGH
env
toolchain report
runtime
runtime report
paths
workspace report
signup
register a member
help
show usage
default
unknown command

블록 arm: 여러 문장을 실행한 뒤 yield가 같은 String 결과 계약을 제공합니다.

화살표 case는 다음 case로 흐르지 않으며 선택된 값 하나만 label 초기화에 사용된다.


범위 조건과 if

switch의 후보는 같은 선택 값에 대한 닫힌 목록일 때 가장 잘 읽힙니다.

age < 18, age <= 120처럼 범위를 순서대로 제거하는 조건은 if/else if가 직접적입니다.

반대로 command.equals("env"), command.equals("runtime")을 반복하는 코드는 switch가 비교 대상을 한 번만 보여 줍니다.

문제우선할 표현이유
1–120세 범위 검증if서로 다른 비교식과 순서가 핵심
명령 문자열 선택switch 식같은 값의 후보 목록
메뉴별 여러 문장 실행화살표 switch 문fall-through 없이 블록 실행
두 라벨 중 하나 선택삼항 연산자결과가 정확히 두 값

삼항 연산자의 범위

src/TernaryLabel.java
public final class TernaryLabel {
    public static void main(String[] args) {
        int age = 20;
        boolean adult = age >= 18;

        String status = adult ? "adult" : "underage";
        System.out.println(status);
    }
}

조건이 참이면 ?와 : 사이 값을, 거짓이면 : 뒤 값을 선택합니다.

삼항 연산자도 값 하나를 만드는 식이므로 변수 초기화나 메서드 인자에 사용할 수 있습니다.

세 단계 이상을 중첩하면 조건과 결과의 짝이 빠르게 흐려집니다.

String label = age < 18 ? "underage"
    : age < 65 ? "adult"
    : "senior";

문법상 동작하지만 분기 순서와 범위를 검토하기는 if/else if가 쉽습니다.

“참과 거짓에 각각 짧은 값 하나”라는 형태를 벗어나면 조건문이나 switch 식으로 풀어냅니다.

출력이나 상태 변경을 삼항 연산자의 두 분기에 넣지 않습니다.


명령 라우팅과 상태 라벨

CLI의 바깥 switch는 어떤 기능을 호출할지 고르고, register 안의 if는 입력 범위를 검증하며, 삼항 연산자는 성인 여부 라벨 두 개 중 하나를 고릅니다.

서로 다른 판단을 한 표현에 몰지 않습니다.

이 예제는 register 뒤에 정수 모양의 나이가 들어온다고 가정하며, 숫자 형식이나 int 범위 초과로 발생하는 NumberFormatException 복구는 ch3에서 다룹니다.

src/SignupRouting.java
public final class SignupRouting {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("command required");
            return;
        }

        switch (args[0]) {
            case "register" -> register(args);
            case "help" -> System.out.println("register <email> <age>");
            default -> System.out.println("unknown command: " + args[0]);
        }
    }

    private static void register(String[] args) {
        if (args.length != 3) {
            System.out.println("register requires email and age");
            return;
        }

        String email = args[1];
        int age = Integer.parseInt(args[2]);
        if (email.isBlank() || age < 1 || age > 120) {
            System.out.println("invalid member");
            return;
        }

        String status = age >= 18 ? "adult" : "underage";
        System.out.println(email + ":" + age + ":" + status);
    }
}
대표 실행
> java -cp out SignupRouting register switch@example.com 20
switch@example.com:20:adult

> java -cp out SignupRouting help
register <email> <age>

연습 문제

FormatSelector를 작성하세요.

첫 인자는 plain, compact, debug 중 하나이며 switch 식으로 라벨을 만듭니다.

둘째 인자 나이가 18세 이상이면 삼항 연산자로 adult, 아니면 underage를 선택합니다.

알 수 없는 형식은 형식 라벨을 unsupported로 정하고 나이 라벨과 함께 출력합니다.

해설 보기
src/FormatSelector.java
public final class FormatSelector {
    public static void main(String[] args) {
        String format = args[0];
        int age = Integer.parseInt(args[1]);

        String formatLabel = switch (format) {
            case "plain" -> "text";
            case "compact" -> "one-line";
            case "debug" -> "diagnostic";
            default -> "unsupported";
        };
        String ageLabel = age >= 18 ? "adult" : "underage";

        System.out.println(formatLabel + ":" + ageLabel);
    }
}

java -cp out FormatSelector debug 20은 diagnostic:adult를 출력합니다.

형식 후보는 switch에, 연속적인 나이 범위의 두 결과는 비교와 삼항 연산자에 남아 판단의 종류가 분리됩니다.