4.4 Switch 표현식과 패턴 매칭 (Java 14+)
자바 14부터 기존 switch 문이 대대적으로 업그레이드되었습니다. 이제 switch는 단순한 문(Statement) 이 아니라 값을 반환하는 표현식(Expression) 으로도 사용할 수 있습니다. 자바 21에서는 패턴 매칭까지 지원하여 더욱 강력해졌습니다.
1. 기존 switch 문의 문제점
기존 switch는 break를 빠뜨리면 다음 case까지 실행되는 fall-through 버그가 발생하기 쉬웠습니다.
// 기존 방식 - fall-through 버그 위험
int day = 2;
String dayName;
switch (day) {
case 1:
dayName = "월요일";
break; // 이걸 빠뜨리면 다음 case로 넘어가버림!
case 2:
dayName = "화요일";
break;
case 3:
dayName = "수요일";
break;
default:
dayName = "기타";
}
또한 dayName에 값을 넣는 방식이라 변수를 먼저 선언하고, 각 case에서 대입하는 구조가 장황합니다.
2. Switch 표현식 (Java 14+) - 화살표(->) 문법
새로운 화살표 문법은 break 없이도 fall-through 없이 각 case가 독립적으로 동작합니다. 그리고 값을 직접 반환 할 수 있습니다.
// 새로운 방식 - 간결하고 안전!
int day = 2;
String dayName = switch (day) {
case 1 -> "월요일";
case 2 -> "화요일";
case 3 -> "수요일";
case 4 -> "목요일";
case 5 -> "금요일";
case 6 -> "토요일";
case 7 -> "일요일";
default -> "잘못된 요일";
};
System.out.println(dayName); // 화요일
여러 case를 하나로 묶기
int day = 6;
String type = switch (day) {
case 1, 2, 3, 4, 5 -> "평일"; // 쉼표로 여러 case 묶기
case 6, 7 -> "주말";
default -> "잘못된 요일";
};
System.out.println(type); // 주말
3. yield 키워드 - 블록 내에서 값 반환
화살표 뒤에 여러 줄의 코드가 필요할 때는 { } 블록을 사용하고, yield 키워드로 값을 반환합니다.
int score = 85;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> {
System.out.println("잘 했어요! B등급입니다.");
yield "B"; // 블록 안에서는 yield로 반환
}
case 7 -> "C";
case 6 -> "D";
default -> {
System.out.println("분발해주세요!");
yield "F";
}
};
System.out.println("등급: " + grade); // 등급: B
전통적인 switch 문에서는 break로 탈출합니다. switch 표현식의 블록에서는 yield로 값을 반환합니다. 두 키워드의 역할이 다름을 구분하세요.
4. Switch 표현식 실전 패턴
패턴 1: HTTP 상태 코드 처리
public class HttpStatusHandler {
static String getStatusMessage(int code) {
return switch (code) {
case 200 -> "OK - 요청 성공";
case 201 -> "Created - 리소스 생성됨";
case 204 -> "No Content - 내용 없음";
case 400 -> "Bad Request - 잘못된 요청";
case 401 -> "Unauthorized - 인증 필요";
case 403 -> "Forbidden - 권한 없음";
case 404 -> "Not Found - 리소스 없음";
case 500 -> "Internal Server Error - 서버 오류";
case 503 -> "Service Unavailable - 서비스 불가";
default -> {
if (code >= 100 && code < 200) yield "1xx - 정보 응답";
else if (code >= 200 && code < 300) yield "2xx - 성공";
else if (code >= 300 && code < 400) yield "3xx - 리다이렉션";
else if (code >= 400 && code < 500) yield "4xx - 클라이언트 오류";
else if (code >= 500 && code < 600) yield "5xx - 서버 오류";
else yield "알 수 없는 상태 코드";
}
};
}
public static void main(String[] args) {
System.out.println(getStatusMessage(200)); // OK - 요청 성공
System.out.println(getStatusMessage(404)); // Not Found - 리소스 없음
System.out.println(getStatusMessage(502)); // 5xx - 서버 오류
}
}
패턴 2: enum과 switch 표현식
public class SeasonInfo {
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
public static void main(String[] args) {
Season current = Season.SUMMER;
String description = switch (current) {
case SPRING -> "봄: 따뜻하고 꽃이 핍니다.";
case SUMMER -> "여름: 덥고 습합니다.";
case AUTUMN -> "가을: 선선하고 단풍이 듭니다.";
case WINTER -> "겨울: 춥고 눈이 옵니다.";
// enum의 모든 값을 처리했으므로 default 불필요
};
int avgTemp = switch (current) {
case SPRING -> 15;
case SUMMER -> 30;
case AUTUMN -> 18;
case WINTER -> -2;
};
System.out.println(description);
System.out.println("평균 기온: " + avgTemp + "°C");
}
}
enum의 모든 상수를 case로 처리했다면 default를 생략할 수 있습니다. 나중에 enum에 새 값을 추가하면 컴파일러가 처리되지 않은 case 를 경고로 알려줍니다. 이것이 enum + switch 표현식의 가장 큰 장점입니다.
패턴 3: 계산기
public class Calculator {
static double calculate(double a, char op, double b) {
return switch (op) {
case '+' -> a + b;
case '-' -> a - b;
case '*' -> a * b;
case '/' -> {
if (b == 0) {
System.out.println("0으로 나눌 수 없습니다.");
yield Double.NaN;
}
yield a / b;
}
default -> {
System.out.println("지원하지 않는 연산자: " + op);
yield Double.NaN;
}
};
}
public static void main(String[] args) {
System.out.println(calculate(10, '+', 3)); // 13.0
System.out.println(calculate(10, '/', 0)); // 0으로 나눌 수 없습니다. / NaN
System.out.println(calculate(7, '*', 6)); // 42.0
}
}
5. instanceof 패턴 매칭 (Java 16+)
기존에는 instanceof 검사 후 별도로 캐스팅해야 했습니다. 이제는 한 번에 검사와 변수 선언을 할 수 있습니다.
// 기존 방식 (3단계)
Object obj = "Hello, Java!";
if (obj instanceof String) { // 1. 타입 확인
String s = (String) obj; // 2. 수동 캐스팅
System.out.println(s.length()); // 3. 사용
}
// 패턴 매칭 (Java 16+) - 1단계로!
if (obj instanceof String s) { // 검사 + 캐스팅 + 변수 선언 동시에!
System.out.println(s.length()); // s를 바로 사용
}
실전 예제: 다양한 도형의 넓이 계산
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
public class PatternMatchingExample {
static double getArea(Shape shape) {
if (shape instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle r) {
return r.width() * r.height();
} else if (shape instanceof Triangle t) {
return 0.5 * t.base() * t.height();
}
throw new IllegalArgumentException("알 수 없는 도형");
}
public static void main(String[] args) {
System.out.println(getArea(new Circle(5))); // 78.53...
System.out.println(getArea(new Rectangle(4, 6))); // 24.0
System.out.println(getArea(new Triangle(3, 8))); // 12.0
}
}
6. Switch를 이용한 패턴 매칭 (Java 21)
자바 21에서는 switch 표현식에서 타입 패턴 매칭이 정식 지원됩니다. instanceof 체인 없이 한 번에 타입별 처리가 가능합니다.
static String describe(Object obj) {
return switch (obj) {
case Integer i -> "정수: " + i;
case Double d -> "실수: " + d;
case String s -> "문자열 (길이 " + s.length() + "): " + s;
case int[] arr -> "int 배열 (길이 " + arr.length + ")";
case null -> "null 값";
default -> "알 수 없는 타입: " + obj.getClass().getName();
};
}
public static void main(String[] args) {
System.out.println(describe(42)); // 정수: 42
System.out.println(describe(3.14)); // 실수: 3.14
System.out.println(describe("Hello")); // 문자열 (길이 5): Hello
System.out.println(describe(null)); // null 값
}
가드 조건 (Guarded Patterns) - Java 21
case 조건에 when 절로 추가 조건을 걸 수 있습니다.
static String classifyNumber(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "음수: " + i;
case Integer i when i == 0 -> "영(0)";
case Integer i when i > 0 -> "양수: " + i;
case Double d when d.isNaN() -> "NaN (숫자 아님)";
default -> "정수/실수 아님";
};
}
System.out.println(classifyNumber(-5)); // 음수: -5
System.out.println(classifyNumber(0)); // 영(0)
System.out.println(classifyNumber(42)); // 양수: 42
7. switch vs if-else 선택 기준
| 상황 | 권장 방식 |
|---|---|
| 특정 값과 동등 비교 (3개+) | switch 표현식 |
범위 조건 (>, <, >=) | if-else if |
복잡한 복합 조건 (&&, ||) | if-else if |
| enum 타입 분기 | switch 표현식 (default 생략 가능) |
| 타입별 분기 처리 | switch 패턴 매칭 (Java 21) |
| 단순 2가지 선택 | 삼항 연산자 또는 if-else |
switch 표현식을 쓸 때 주의할 점:
- switch 표현식은 모든 가능한 경우를 처리해야 합니다.
default가 없으면 컴파일 에러가 납니다. String,enum,정수형 타입,wrapper 타입, 그리고 Java 21부터는 모든Object를 switch할 수 있습니다.- enum + switch 표현식 조합은 실무에서 가장 자주 쓰이는 패턴입니다. enum에 새 상수를 추가하면 컴파일러가 처리 안 된 case를 바로 알려줍니다.
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
Season season = Season.SUMMER;
int avgTemp = switch (season) {
case SPRING -> 15;
case SUMMER -> 30;
case AUTUMN -> 18;
case WINTER -> -2;
// default 없어도 됨 - 모든 enum 값이 처리되므로
};