모든 경우를 다뤘는지 컴파일러가 검사하는 것. Java 21의 sealed + switch 패턴 매칭이 Visitor를 밀어낸 근거다.
sealed interface Shape permits Circle, Rect, Triangle {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
record Triangle(double b, double h) implements Shape {}
double area(Shape s) {
return switch (s) { // default 가 없어도 된다
case Circle c -> Math.PI * c.r() * c.r();
case Rect r -> r.w() * r.h();
case Triangle t -> t.b() * t.h() / 2;
};
}
Visitor가 존재했던 이유의 절반
Visitor 가 풀던 문제
- ① 타입 계층 밖에 연산을 추가한다 (연산 추가가 쉬워진다)
- ② 모든 타입을 빠짐없이 처리하게 강제한다
sealed + switch 는 ②를 언어가 해결한다 permits 로 하위 타입이 고정되므로 컴파일러가 '이 switch 는 Triangle 을 안 다룬다' 를 알 수 있다
새 타입을 추가할 때
-
Visitor — 모든 Visitor 구현을 고쳐야 한다
- 인터페이스에 메서드를 추가하지 않으면 컴파일은 통과한다
- 빠뜨린 구현을 런타임에야 발견한다
-
sealed+switch — 해당 switch 가 컴파일 오류를 낸다
- 빠뜨릴 수 없다
default를 쓰면 검사가 사라진다
switch (s) {
case Circle c -> ...;
case Rect r -> ...;
default -> 0; // ← Triangle 을 추가해도 컴파일이 통과한다
}
망라성의 이점을 얻으려면 default를 쓰지 않는다. 봉인된 계층에서 default는 안전망이 아니라 안전장치를 끄는 스위치다.
언제 여전히 Visitor인가
- 타입 계층을 봉인할 수 없다 (외부 라이브러리 · 확장 가능해야 함)
- 연산이 매우 많고 각각이 상태를 갖는다 (Visitor 객체가 누적 상태를 보관)
- 순회 로직 자체가 복잡하다 (AST 순회 — 컴파일러 · 린터)
반대로 타입이 고정된 도메인 모델(주문 상태 · 결제 결과 · 명령 종류) 이면 대부분 sealed + switch 가 낫다
왜 지금에서야 가능해졌나
컴파일러가 망라성을 판단하려면 '이 인터페이스를 구현할 수 있는 타입이 전부 무엇인지' 를 알아야 한다
자바는 오랫동안 그것을 알 수 없었다 — 누구나 구현할 수 있었기 때문이다 sealed(Java 17) 가 permits 로 하위 타입을 못 박으면서 비로소 컴파일러가 '전부' 를 셀 수 있게 됐다
sealed(17) → 레코드 패턴·switch 패턴 매칭(21) 로 이어지는 순서에는 이런 의존 관계가 있다
null 처리가 달라진다
switch (shape) {
case null -> "없음"; // Java 21 부터 case null 을 쓸 수 있다
case Circle c -> ...;
case Rect r -> ...;
case Triangle t-> ...;
}
case null 을 안 쓰면 switch 는 null 에서 NullPointerException 을 던진다 (기존 switch 와 동일한 동작을 유지한 것이다)
의도적으로 null 을 다루려면 명시해야 한다 — 실수로 빠뜨릴 수 없게 만든 설계
레코드 패턴과 조합
static String describe(Shape s) {
return switch (s) {
case Circle(double r) when r > 100 -> "큰 원";
case Circle(double r) -> "원 " + r;
case Rect(double w, double h) -> w + "x" + h;
case Triangle t -> "삼각형";
};
}
분해와 조건(when)을 함께 쓰면 Visitor 로는 장황했던 코드가 한 화면에 들어온다.
면접 함정
- ❌ "switch 패턴 매칭은 문법 설탕" → 망라성 검사라는 타입 시스템 기능이 핵심이다.
- ❌ "sealed는 final과 비슷하다" →
final은 상속 금지,sealed는 허용 목록을 정하는 것이다.