* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
(자바 버전 여행 ⑱)
긴 if (o instanceof A) ... else if (o instanceof B) ... 사다리를 써 본 적 있을 것이다. 자바 21의 switch 패턴 매칭은 이 사다리를 평평한 switch로 편다. sealed·record 패턴과 만나면 진가가 나온다.
타입으로 분기하기
// Before — instanceof 사다리
String format(Object o) {
if (o instanceof Integer i) return "정수 " + i;
else if (o instanceof String s) return "문자열 " + s;
else if (o instanceof int[] a) return "배열 " + a.length;
else return "모름";
}
// After — switch 패턴 매칭
String format(Object o) {
return switch (o) {
case Integer i -> "정수 " + i;
case String s -> "문자열 " + s;
case int[] a -> "배열 " + a.length;
default -> "모름";
};
}
when 가드 — 조건을 덧붙이다
같은 타입 안에서 값에 따라 더 나누고 싶으면 when을 붙인다.
String grade(Object o) {
return switch (o) {
case Integer i when i >= 90 -> "A";
case Integer i when i >= 80 -> "B";
case Integer i -> "C";
default -> "?";
};
}
null도 case로
예전 switch는 null이 들어오면 무조건 NullPointerException이었다. 21에선 case null로 명시해 다룰 수 있다.
switch (name) {
case null -> System.out.println("이름 없음");
case "cozmo" -> System.out.println("주인장");
default -> System.out.println("손님: " + name);
}
그리고 대상이 sealed면, 모든 하위 타입을 다룰 때 default가 필요 없다. 나중에 타입이 추가되면 컴파일러가 빠진 case를 알려준다 — 이게 패턴 매칭 switch의 진짜 값어치다.
직접 해보기 — instanceof 사다리를 switch로
긴 분기를 직접 접어 보자.
Object를 받아 Integer/String/List를 구분해 문자열을 돌려주는 메서드를 instanceof 사다리로 짠다.- 그걸 switch 패턴 매칭으로 바꾼다.
- Integer에
when가드로 양수/음수를 더 나눈다. case null을 추가해 null 입력도 안전하게 처리한다.
static String tell(Object o) {
return switch (o) {
case null -> "널";
case Integer i when i < 0 -> "음수 " + i;
case Integer i -> "양수/0 " + i;
case String s -> "문자열(" + s.length() + ")";
default -> "기타 " + o.getClass().getSimpleName();
};
}
// tell(-3) -> "음수 -3" / tell("hi") -> "문자열(2)" / tell(null) -> "널"
실행해 보면 이렇게 나온다.
음수 -3
문자열(2)
널
여기서 조금씩 바꿔 보자.
case Integer i(가드 없는 것)를 가드 있는 것들보다 위로 옮겨 보고, 왜 컴파일 에러(도달 불가)가 나는지 본다.- 대상 타입을 sealed 인터페이스로 바꿔 default를 없애 본다.
한 걸음 더 — record 패턴과 섞어 case Point(var x, var y) when x == y -> ... 처럼 분해+가드를 한 줄에 써 보자.
✅ 확인 — switch 패턴 매칭의 타입 분기·when 가드·case null을 설명할 수 있으면 통과다.
sealed면 default가 필요 없다
대상이 sealed 계층이면 컴파일러가 모든 하위 타입을 다뤘는지 검사한다. 다 다루면 default 없이 컴파일되고, 나중에 새 타입을 추가하면 그걸 안 다룬 switch가 컴파일 에러가 된다 — 놓친 경우를 런타임이 아니라 컴파일 타임에 잡는 것이다. 반대로 Object 같은 개방형이면 default가 있어야 한다.
sealed interface Shape permits Circle, Square {}
String describe(Shape s) {
return switch (s) {
case Circle c -> "원";
case Square q -> "사각형";
}; // sealed라 default 없이도 OK — Square 추가하면 여기가 컴파일 에러
}
record 패턴과 결합 — 분해까지 한 번에
switch 패턴에 record 패턴을 얹으면 타입 확인·분해·분기가 한 문장에 담긴다. 타입이 맞으면 그 자리에서 값을 꺼낸다. 중첩 record도 한 번에 풀린다.
record Point(int x, int y) {}
String loc(Object o) {
return switch (o) {
case Point(int x, int y) when x == y -> "대각선 위 (" + x + ")";
case Point(int x, int y) -> "(" + x + ", " + y + ")";
default -> "알 수 없음";
};
}
when 가드로 값 조건까지
타입만이 아니라 ‘이 타입이면서 값이 이런 경우’는 when 가드로 붙인다. case Integer i when i > 100 -> "큰 수"처럼. 순서에 주의하자 — 구체적인 가드를 위에, 일반적인 경우를 아래에 둔다(위에서부터 매치). 겹쳐서 도달 불가능한 case가 생기면 컴파일러가 경고한다.
언제 쓰나 — 다형성 대신
전통적으로 ‘타입별 다른 동작’은 상속·오버라이딩(다형성)으로 풀었다. 하지만 타입이 내 통제 밖(라이브러리 클래스)이거나, 동작이 데이터 바깥에 있는 게 자연스러울 때는 switch 패턴이 낫다. 데이터(record·sealed)와 동작(switch)을 분리하는 스타일이다. 둘은 대립이 아니라 상황에 맞게 고르는 도구다.
패턴 매칭은 여기서 일단락이다. 다음 편은 오래 묵은 불편을 정리한 작은 개선, Sequenced Collections다.
자주 묻는 질문 (FAQ)
switch로 타입 분기를 할 수 있나요?
네, 자바 21부터 case에 타입 패턴을 쓸 수 있습니다. 긴 instanceof 사다리를 평평한 switch로 바꿉니다.
switch에서 null을 다룰 수 있나요?
case null로 명시하면 됩니다. 예전엔 null이 무조건 예외였지만 이제 안전하게 분기할 수 있습니다.

답글 남기기