switch 문·switch 식과 삼항 연산자
한 값을 여러 후보와 비교하는 switch 문과 switch 식의 차이, break와 fall-through, yield를 익히고 두 값 중 하나를 고르는 삼항 연산자를 언제 쓰는지 배웁니다.
if와 else로 조건 분기하기에서 본 조건 사슬은 범위를 나누는 데 잘 맞습니다. 그런데 하나의 값을 여러 후보와 하나씩 비교할 때는 사정이 다릅니다. 명령행에서 받은 명령 문자열 command에 따라 설명 문구를 고르는 코드를 if로 쓰면 다음과 같습니다.
if (command.equals("env")) {
label = "toolchain report";
} else if (command.equals("runtime")) {
label = "runtime report";
} else if (command.equals("paths")) {
label = "workspace report";
} else {
label = "unknown command";
}증감·비교·논리·대입 연산자에서 말했듯이 문자열의 내용은 equals로 비교합니다. 이 코드는 올바르게 동작하지만, 줄마다 command.equals(...)가 되풀이되어 정작 중요한 후보 목록이 눈에 잘 들어오지 않습니다. 또 중간의 한 줄만 다른 변수를 비교하도록 잘못 써도 모양이 비슷해서 알아차리기 어렵습니다.
switch는 비교할 값을 맨 위에 한 번만 쓰고, 그 아래에 후보와 후보마다 할 일을 나열합니다. Java의 switch에는 두 가지 모양이 있습니다. 하나는 Java 초기부터 있던 콜론(:)형이고, 다른 하나는 Java 14에서 정식 기능이 된 화살표(->)형입니다. 이 글에서는 두 모양이 어떻게 다르게 실행되는지 보고, switch로 값을 만들어 내는 switch 식(switch expression)과, 두 값 중 하나를 고르는 삼항 연산자를 배웁니다.
콜론형 switch 문은 일치한 case부터 끝까지 실행한다
먼저 콜론형부터 보겠습니다. switch (menu)는 괄호 안의 값을 계산한 뒤, 같은 값이 적힌 case를 찾아 그 위치부터 실행합니다. 일치하는 case가 없으면 default부터 실행합니다. 다음 코드는 메뉴 번호에 따라 메뉴 이름을 출력하려고 쓴 것입니다. menu가 1이면 무엇이 출력될지 예상해 보세요.
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
unknownsignup 하나만 출력될 것 같지만 네 줄이 모두 출력됩니다. 콜론형 switch에서 case 1:은 독립된 블록이 아니라 '여기서부터 실행하라'는 진입 위치 표시일 뿐이기 때문입니다. case 1:에서 실행을 시작한 뒤에는 다음 case 2:를 만나도 멈추지 않고, menu가 2인지 다시 비교하지도 않은 채 아래 문장을 계속 실행합니다. 그래서 switch 블록의 끝에 도달할 때까지 members, quit, unknown이 차례로 출력됩니다. 이렇게 다음 case의 문장까지 이어서 실행되는 것을 폴스루(fall-through)라고 합니다.
break로 switch 빠져나오기
일치한 case의 문장만 실행하려면 각 case의 끝에 break를 둡니다. break를 만나면 switch 문이 그 자리에서 끝나고, switch 다음 문장으로 넘어갑니다.
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");
}
}
}signupcase 1:에서 signup을 출력한 직후 break가 switch를 끝내므로 나머지 case는 실행되지 않습니다. 마지막 default에는 break가 없지만, 그 아래에 더 실행할 문장이 없으니 결과는 같습니다. 아래 그림에서 같은 menu = 1이 break가 있을 때와 없을 때 각각 어디까지 실행되는지 비교해 보세요.
case 1 진입 뒤 break 유무가 실행 범위를 바꾼다
case 1 진입 뒤 break 없음은 네 출력을 계속하고 break는 signup 뒤 탈출합니다. 같은 switch 위치에서 실행 이동·출력을 추적하기 적합합니다.
FLOWCHART · FALL-THROUGH
case 1 진입 뒤 break 유무가 실행 범위를 바꾼다
콜론형 switch는 일치한 case에서 진입하지만 다음 case label에서 자동으로 멈추지 않는다. 같은 menu 1도 break 유무에 따라 출력 범위가 달라진다.
break 없음 · menu = 1
signupmembersquitunknown- switch 뒤로 이동
case 1에 break · menu = 1
signupbreak로 switch 종료- switch 뒤로 이동
case 2, case 0, default는 실행하지 않습니다.
case label은 진입점을 고르고, break는 가장 가까운 switch의 실행 범위를 끝낸다.
이 예의 조건
- 두 콜론형 switch는 menu=1이라는 같은 입력입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | case 1 진입 뒤 break 유무가 실행 범위를 바꾼다 콜론형 switch의 fall-through와 break 실행 범위 비교 FLOWCHART · FALL-THROUGH case 1 진입 뒤 break 유무가 실행 범위를 바꾼다 콜론형 switch는 일치한 case에서 진입하지만 다음 case label에서 자동으로 멈추지 않는다. 같은 menu 1도 break 유무에 따라 출력 범위가 달라진다. break 없음 · menu = 1 signup members quit unknown switch 뒤로 이동 case 1에 break · menu = 1 signup break 로 switch 종료 switch 뒤로 이동 case 2, case 0, default는 실행하지 않습니다. case label은 진입점을 고르고, break 는 가장 가까운 switch의 실행 범위를 끝낸다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| case 1 진입 일치한 case 1부터 signup을 출력합니다. |
|
|
| 이후 실행 break 없는 경로는 다음 case 본문을 계속 실행합니다. |
|
|
| switch 이후 두 경로가 switch 다음 문장으로 이동합니다. |
|
|
case 1 진입 뒤 break 유무가 실행 범위를 바꾼다
case 1 진입 뒤 break 없음은 네 출력을 계속하고 break는 signup 뒤 탈출합니다. 같은 switch 위치에서 실행 이동·출력을 추적하기 적합합니다.
FLOWCHART · FALL-THROUGH
case 1 진입 뒤 break 유무가 실행 범위를 바꾼다
콜론형 switch는 일치한 case에서 진입하지만 다음 case label에서 자동으로 멈추지 않는다. 같은 menu 1도 break 유무에 따라 출력 범위가 달라진다.
break 없음 · menu = 1
signupmembersquitunknown- switch 뒤로 이동
case 1에 break · menu = 1
signupbreak로 switch 종료- switch 뒤로 이동
case 2, case 0, default는 실행하지 않습니다.
case label은 진입점을 고르고, break는 가장 가까운 switch의 실행 범위를 끝낸다.
이 예의 조건
- 두 콜론형 switch는 menu=1이라는 같은 입력입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | case 1 진입 뒤 break 유무가 실행 범위를 바꾼다 콜론형 switch의 fall-through와 break 실행 범위 비교 FLOWCHART · FALL-THROUGH case 1 진입 뒤 break 유무가 실행 범위를 바꾼다 콜론형 switch는 일치한 case에서 진입하지만 다음 case label에서 자동으로 멈추지 않는다. 같은 menu 1도 break 유무에 따라 출력 범위가 달라진다. break 없음 · menu = 1 signup members quit unknown switch 뒤로 이동 case 1에 break · menu = 1 signup break 로 switch 종료 switch 뒤로 이동 case 2, case 0, default는 실행하지 않습니다. case label은 진입점을 고르고, break 는 가장 가까운 switch의 실행 범위를 끝낸다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| case 1 진입 일치한 case 1부터 signup을 출력합니다. |
|
|
| 이후 실행 break 없는 경로는 다음 case 본문을 계속 실행합니다. |
|
|
| switch 이후 두 경로가 switch 다음 문장으로 이동합니다. |
|
|
break는 자기를 감싸고 있는 가장 가까운 switch나 반복문 하나를 끝냅니다. 그래서 반복문 안에 switch가 있고 그 안에서 break를 쓰면, 반복문이 아니라 switch만 빠져나오고 반복은 계속됩니다. 반복문에서 쓰는 break는 while 반복과 break·continue에서 다룹니다.
폴스루를 일부러 쓰는 경우도 있습니다. 여러 후보가 같은 일을 해야 할 때 case 0: 바로 아래에 case 9:를 두면 둘 다 같은 문장을 실행합니다. 하지만 break를 빠뜨린 실수와 의도한 폴스루는 겉모양이 똑같아서, 코드를 읽는 사람이 둘을 구분하기 어렵습니다. 다음은 명령에 맞는 문구를 label에 대입하려다 break 하나를 빠뜨린 경우입니다.
public final class ColonLabelOverwrite {
public static void main(String[] args) {
String command = "signup";
String label;
switch (command) {
case "signup":
label = "register a member";
case "help":
label = "show usage";
break;
default:
label = "unknown command";
}
System.out.println(label);
}
}show usagecase "signup":에서 label에 register a member를 대입했지만, break가 없어 아래로 계속 실행되면서 case "help":의 대입이 그 값을 덮어씁니다. 컴파일 오류도 실행 중 오류도 없이 틀린 값이 출력됩니다. 이런 실수를 구조적으로 막아 주는 것이 다음에 볼 화살표형입니다.
화살표형 case는 다음 case로 넘어가지 않는다
화살표형에서는 case 값 -> 오른쪽에 실행할 문장 하나나 중괄호 블록을 씁니다. 화살표형 case는 일치했을 때 자기 오른쪽만 실행하고 switch를 끝내므로, 폴스루가 일어나지 않고 break도 필요 없습니다. 여러 후보가 같은 일을 해야 하면 case 0, 9 ->처럼 쉼표로 후보를 나열합니다.
public final class ArrowSwitchStatement {
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, 9 -> System.out.println("quit");
default -> System.out.println("unknown");
}
}
}signupmenu가 1이므로 첫 case의 오른쪽만 실행되고 switch가 끝납니다. menu를 0이나 9로 바꾸면 quit이, 그 밖의 값이면 unknown이 출력됩니다. 한 switch 안에서 콜론형과 화살표형을 섞어 쓸 수는 없습니다.
이 예제처럼 문장을 실행하기만 하는 switch를 switch 문(switch statement)이라고 합니다. 이렇게 상수를 비교하는 switch 문은 모든 경우를 다룰 필요가 없어서, default가 없고 일치하는 case도 없으면 아무것도 실행하지 않고 지나갑니다.
switch 식으로 값 고르기
ColonLabelOverwrite에서 하려던 일은 사실 명령에 맞는 값 하나를 골라 label에 넣는 것이었습니다. 이런 경우에는 switch 전체를 하나의 값으로 쓰는 switch 식이 더 잘 맞습니다. 다음 코드는 실행 인자로 받은 명령에 따라 설명 문구를 고릅니다.
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);
}
}register a member첫 줄의 args.length == 0 ? "help" : args[0]은 인자가 없으면 "help"를, 있으면 첫 인자를 고르는 삼항 연산자입니다. 이 글의 뒤에서 자세히 다룹니다.
switch (command) { ... } 전체는 String 값 하나가 되는 식입니다. command와 일치하는 case를 찾으면 화살표 오른쪽의 값이 이 식의 결과가 되고, 그 결과로 label이 초기화됩니다. signup을 넣으면 네 번째 case가 선택되어 register a member가 출력됩니다. 닫는 중괄호 뒤의 세미콜론은 String label = ...;이라는 선언 문장을 끝내는 것이므로 빠뜨리면 안 됩니다.
콜론형으로 같은 일을 할 때와 비교하면 차이가 분명합니다. label을 값 없이 선언한 뒤 case마다 대입할 필요가 없고, 화살표형이라 폴스루로 값이 덮어써질 일도 없습니다. 게다가 컴파일러가 모든 경우에 값이 정해지는지 검사합니다. switch 식은 어떤 입력에 대해서도 값을 내놓아야 하므로, 문자열이나 int처럼 가능한 값을 모두 나열할 수 없는 타입이라면 default가 반드시 있어야 합니다. default를 지우면 the switch expression does not cover all possible input values라는 컴파일 오류가 납니다.
switch로 문자열을 비교할 때는 ==가 아니라 equals와 같은 방식으로 내용을 비교합니다. 그래서 case "help"는 내용이 help인 문자열이면 어떻게 만들어졌든 일치합니다. 이 글에서 쓰는 case 뒤에는 "help"나 1 같은 상수만 올 수 있고, 같은 값을 두 번 쓰면 컴파일 오류가 납니다.
여러 문장이 필요하면 블록과 yield
화살표 오른쪽에 값 하나만 쓰는 대신, 몇 가지 문장을 실행한 뒤에 값을 내놓고 싶을 때가 있습니다. 그럴 때는 중괄호 블록을 쓰고, 블록 안에서 yield로 그 분기의 결과값을 내놓습니다.
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 postsrole이 member이므로 두 번째 case의 블록이 실행됩니다. 블록 안의 println이 먼저 role=member를 출력하고, yield "write posts";가 이 switch 식의 결과를 정합니다. 그 결과가 description에 들어간 뒤 마지막 줄에서 write posts가 출력됩니다.
yield는 switch 식에 결과값을 넘겨줄 뿐, 메서드를 끝내는 return과는 다릅니다. 실제로 switch 식의 블록 안에서는 return을 쓸 수 없고, 쓰면 컴파일 오류가 납니다. 블록에서 yield를 빠뜨려도 '이 분기가 값을 내놓지 않는다'는 컴파일 오류가 납니다. 정리하면 switch 식의 모든 분기는 값을 내놓거나 예외를 던져서 끝나야 하고, 내놓는 값은 결과를 받는 쪽과 타입이 맞아야 합니다. 예를 들어 String을 고르는 switch 식에서 한 분기만 0을 내놓으면 컴파일되지 않습니다. 예외를 던지는 방법은 12장의 예외에서 다룹니다.
아래 그림은 CommandLabel에서 후보마다 어떤 값이 선택되는지, 그리고 SwitchYield의 블록이 어떻게 값을 내놓는지 정리한 것입니다.
TREE · SWITCH VALUE MAP
각 case는 String 값 하나를 골라 label을 초기화한다
command 값을 평가한 뒤 해당 case의 String 결과 하나로 label을 초기화합니다. 화살표 case는 다음 case로 흐르지 않습니다.
command → 일치하는 case → String label
이 예제에서는 아래 결과 중 하나를 선택합니다.
| case 입력 | 선택되는 String 값 |
|---|---|
"env" | "toolchain report" |
"runtime" | "runtime report" |
"paths" | "workspace report" |
"signup" | "register a member" |
"help" | "show usage" |
default | "unknown command" |
"env""toolchain report""runtime""runtime report""paths""workspace report""signup""register a member""help""show usage"default"unknown command"
여러 문장이 필요한 분기는 yield로 값을 제공합니다
println("role=" + role);
yield "write posts";
본문의 별도 SwitchYield 예제에서는 role이 member일 때 메시지를 먼저 출력하고, yield가 String 결과를 제공합니다. yield는 메서드를 끝내는 return과 다릅니다.
화살표 case는 다음 case로 흐르지 않으며 선택된 값 하나만 label 초기화에 사용된다.
더 깊이: switch에 쓸 수 있는 값의 타입
이 글처럼 case 뒤에 상수를 쓰는 switch의 괄호에는 char, byte, short, int와 이들의 래퍼 클래스, String, 그리고 10장에서 배울 열거형(enum) 값을 넣을 수 있습니다. long, float, double, boolean은 넣을 수 없어서 long 변수로 switch를 쓰면 컴파일 오류가 납니다. 모든 기본형을 switch에 쓸 수 있게 넓히는 기능(JEP 507)은 Java 25에서도 아직 프리뷰 단계라서, 따로 켜지 않으면 쓸 수 없습니다.
Java 21부터는 그 밖의 참조 타입도 괄호에 넣을 수 있습니다. 다만 그때는 상수 대신 값의 타입에 따라 분기하는 패턴 매칭(pattern matching)을 case에 씁니다. 또 괄호 안의 값이 null이면 원래는 case를 비교하기 전에 NullPointerException이 발생하는데, Java 21부터는 case null ->로 null을 직접 처리할 수도 있습니다. 실행 인자 args의 원소는 null이 될 수 없으므로 이 글의 예제에서는 문제가 되지 않습니다. 패턴 매칭은 패턴 매칭과 데이터 분기에서 다룹니다.
if와 switch 중 무엇을 쓸까
switch가 잘 읽히는 것은 같은 값 하나를 고정된 후보 목록과 비교할 때입니다. 반대로 age < 18, age <= 120처럼 범위를 순서대로 걸러 내는 조건은 이 글의 switch로 쓸 수 없습니다. 상수 case로는 '18 미만' 같은 범위를 적을 방법이 없기 때문입니다. 이럴 때는 if-else if 사슬을 씁니다.
| 문제 | 알맞은 표현 | 이유 |
|---|---|---|
| 1–120세 범위 검증 | if-else if | 서로 다른 비교식과 검사 순서가 핵심 |
| 명령 문자열에 맞는 문구 고르기 | switch 식 | 같은 값의 후보 목록에서 값 하나를 고름 |
| 메뉴 번호마다 다른 문장 실행 | 화살표형 switch 문 | 폴스루 없이 후보별 문장 실행 |
| 두 라벨 중 하나 고르기 | 삼항 연산자 | 결과가 정확히 둘 |
삼항 연산자로 두 값 중 하나 고르기
결과가 둘뿐이라면 switch도 if도 필요 이상으로 깁니다. 삼항 연산자(ternary operator)는 조건 ? 값1 : 값2 모양으로, 조건이 참이면 값1을, 거짓이면 값2를 결과로 내놓습니다. 피연산자가 조건, 값1, 값2 세 개라서 삼항 연산자라고 부르며, Java 언어 명세에서는 조건 연산자(conditional operator)라고 부릅니다.
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);
}
}adultadult가 참이므로 ?와 : 사이의 "adult"가 선택되어 status에 들어갑니다. age를 15로 바꾸면 : 뒤의 "underage"가 선택됩니다. 같은 일을 if-else로 하려면 status를 먼저 선언하고 두 블록에서 각각 대입해야 하지만, 삼항 연산자는 그 자체가 값이므로 변수를 초기화하는 자리에 바로 쓸 수 있습니다. System.out.println(adult ? "adult" : "underage")처럼 메서드에 넘기는 값으로 쓸 수도 있습니다.
삼항 연산자는 선택된 쪽의 값만 계산합니다. 그래서 &&처럼 가드로도 쓸 수 있습니다. 예를 들어 int average = memberCount == 0 ? 0 : totalAge / memberCount;는 회원이 없으면 0을 고르고 나눗셈은 계산하지 않으므로, memberCount가 0이어도 예외가 나지 않습니다.
삼항 연산자를 이어 붙이면 세 갈래 이상도 표현할 수 있습니다.
String label = age < 18 ? "underage"
: age < 65 ? "adult"
: "senior";이 코드도 else if 사슬과 똑같이 위에서부터 조건을 검사해 올바른 라벨을 고릅니다. 하지만 조건과 결과의 짝이 ?와 :로만 구분되어서, 갈래가 늘어날수록 어떤 범위가 어떤 결과로 가는지 확인하기가 if-else if보다 훨씬 어렵습니다. 삼항 연산자는 조건 하나에 짧은 값 두 개를 고를 때만 쓰고, 그보다 복잡하면 if나 switch 식으로 풀어 씁니다.
삼항 연산자는 값을 만드는 식이라서 그 자체로는 문장이 될 수 없고, 두 자리에도 할 일이 아니라 값이 와야 합니다. 그래서 adult ? System.out.println("adult") : System.out.println("underage");처럼 쓰면 not a statement라는 컴파일 오류가 납니다. 출력할 문구를 고르는 것이 목적이라면 값만 고르고, 출력은 바깥에서 한 번만 합니다. count++ 같은 부수 효과는 넣을 수는 있지만, 어느 쪽이 실행될지 조건에 따라 달라지므로 넣지 않는 편이 좋습니다.
더 깊이: 삼항 연산자의 결과 타입
두 값의 타입이 다르면 결과 타입이 예상과 달라질 수 있습니다. 예를 들어 System.out.println(true ? 1 : 2.0);은 1이 아니라 1.0을 출력합니다. int와 double이 섞인 삼항 연산자는 둘 중 넓은 타입인 double을 결과 타입으로 정하기 때문에, 조건이 참이라 1이 선택되어도 double 값 1.0이 됩니다. 산술 연산에서 int와 double을 섞으면 double로 계산되는 것과 같은 규칙입니다. 두 자리의 타입을 맞춰 두면 이런 혼동이 생기지 않습니다.
명령 라우팅: switch, if, 삼항 연산자를 함께 쓰기
이제 세 가지 도구를 각자 잘하는 일에 맞춰 함께 써 보겠습니다. 다음 프로그램은 첫 인자로 명령을 받아 해당 기능을 실행합니다. 바깥의 switch는 어떤 기능을 실행할지 고르고, register 안의 if는 입력 범위를 검사하며, 삼항 연산자는 성인 여부 라벨 두 개 가운데 하나를 고릅니다. 이 예제는 register 뒤의 나이가 int 범위의 정수 문자열이라고 가정합니다. 숫자가 아닌 문자열 때문에 생기는 NumberFormatException을 처리하는 방법은 3장의 입력 검증과 재입력 반복에서 다룹니다.
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);
}
}main은 먼저 인자가 하나도 없는지 확인합니다. 인자가 없으면 args[0]을 읽을 수 없으므로 안내를 출력하고 return으로 main을 끝냅니다. 그다음 화살표형 switch 문이 첫 인자에 따라 할 일을 고르는데, 이렇게 명령에 따라 실행할 기능을 고르는 일을 라우팅(routing)이라고도 합니다.
case "register"의 register(args)는 아래에 정의한 register라는 기능을 호출하면서 인자 배열을 그대로 넘깁니다. 배열에는 main이 받은 인자가 모두 남아 있으므로 args[0]은 register, 이메일은 args[1], 나이는 args[2]입니다. 이처럼 이름을 붙여 따로 정의한 기능을 메서드라고 하는데, 메서드를 만드는 문법은 3장의 메서드와 매개변수에서 배웁니다. 여기서는 호출하면 그 메서드의 문장이 실행되고, 끝나면 호출한 자리로 돌아온다는 순서만 알면 됩니다.
register 안에서는 인자 개수, 이메일과 나이 범위를 차례로 검사하고, 문제가 있으면 메시지를 출력한 뒤 return으로 register를 끝냅니다. 이때 return은 register 메서드만 끝내고 호출한 main으로 돌아갑니다. 이 프로그램의 main은 switch 뒤에 할 일이 없으므로 곧이어 종료됩니다. 모든 검사를 통과한 입력만 마지막 두 줄에 도달하고, 삼항 연산자가 고른 라벨과 함께 출력됩니다.
> java -cp out SignupRouting register [email protected] 20
[email protected]:20:adult
> java -cp out SignupRouting register [email protected] 200
invalid member
> java -cp out SignupRouting help
register <email> <age>첫 실행은 명령 선택, register 호출, 인자 수 확인, 이메일과 나이 읽기, 범위 검사, adult 선택, 출력 순서로 진행됩니다. 두 번째 실행은 나이 200이 범위 검사에 걸려 invalid member를 출력하고, 그 자리에서 return으로 register를 끝냅니다. 세 번째 실행은 case "help"가 선택되어 사용법 한 줄만 출력합니다.
정리
- 콜론형
switch의case는 진입 위치일 뿐이라서,break가 없으면 다음case의 문장까지 이어서 실행됩니다(폴스루).break는 가장 가까운switch나 반복문 하나를 끝냅니다. - 화살표형
case는 일치한 오른쪽만 실행하므로 폴스루가 없고break도 필요 없습니다. 여러 후보는case 0, 9 ->처럼 쉼표로 나열합니다. - switch 식은
switch전체가 값 하나가 되는 식입니다. 모든 경우에 값을 내놓아야 하므로 문자열이라면default가 필요하고, 블록에서는yield로 값을 내놓습니다. - 같은 값의 후보 목록에는
switch를, 범위와 순서가 중요한 조건에는if를 씁니다. - 삼항 연산자
조건 ? 값1 : 값2는 결과가 둘일 때 값 하나를 고르는 식입니다. 선택된 쪽만 계산하며, 두 자리에는 값을 씁니다.
조건문을 한 번 실행해 원하는 결과가 나왔다고 해서 모든 입력에서 맞는 것은 아닙니다. 다음 글에서는 결과가 바뀌는 기준값 근처를 골라 조건문을 점검하는 방법을 연습합니다.
연습 문제
문제 1: 형식 라벨과 나이 라벨 고르기
FormatSelector를 작성하세요. 첫 인자는 출력 형식인 plain, compact, debug 중 하나이고, switch 식으로 각각 text, one-line, diagnostic 라벨을 고릅니다. 그 밖의 형식이면 형식 라벨을 unsupported로 정합니다. 둘째 인자인 나이가 18 이상이면 삼항 연산자로 adult를, 아니면 underage를 고른 뒤, 두 라벨을 :로 이어 출력합니다.
해설 보기
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
> java -cp out FormatSelector xml 15
unsupported:underage형식은 정해진 후보 목록에서 하나를 고르는 일이므로 switch 식에 맡기고, 나이는 비교 하나로 결과가 둘로 갈리므로 삼항 연산자에 맡겼습니다. 이렇게 판단의 종류에 맞는 도구를 쓰면 각 줄이 무엇을 고르는지 바로 읽힙니다. default가 없으면 xml 같은 값에 대해 고를 라벨이 없으므로 컴파일되지 않는다는 점도 확인해 보세요.
문제 2: 폴스루 따라가기
본문의 SwitchFallThrough에서 menu를 2로 바꾸면 무엇이 출력되나요? 각 줄이 출력되는 이유를 설명하세요.
해설 보기
members, quit, unknown 세 줄이 출력됩니다. switch는 값이 2인 case 2:를 찾아 그 위치부터 실행을 시작하므로, 그보다 위에 있는 case 1:의 signup은 출력되지 않습니다. case 2:에는 break가 없어서 실행이 다음 case 0:의 quit, 이어서 default:의 unknown까지 내려갑니다. 이때 menu가 0인지 다시 비교하지 않는다는 점이 핵심입니다. case는 처음 진입할 위치를 정할 때만 비교에 쓰입니다.
문제 3: if 사슬을 switch 식으로 바꾸기
다음 코드를 같은 결과를 내는 switch 식으로 바꾸세요. 세 갈래 가운데 ||로 묶인 두 문자열과 마지막 else를 switch 식에서 어떻게 적을지가 핵심입니다.
String level;
if (role.equals("guest")) {
level = "read";
} else if (role.equals("member") || role.equals("writer")) {
level = "write";
} else {
level = "none";
}해설 보기
String level = switch (role) {
case "guest" -> "read";
case "member", "writer" -> "write";
default -> "none";
};role.equals(...)를 되풀이하던 비교가 switch (role) 한 번으로 줄고, ||로 묶었던 두 후보는 case "member", "writer"처럼 쉼표로 나열합니다. 마지막 else는 default가 맡습니다. level을 미리 선언하고 블록마다 대입하던 코드도 선언과 동시에 초기화하는 한 문장이 되었습니다. guest, member, writer, admin을 넣어 두 코드가 각각 read, write, write, none을 내는지 확인해 보세요.