자바 디자인 패턴 용어 사전
패턴 비교exhaustiveness · 망라성 · sealed

망라성 검사

모든 경우를 다뤘는지 컴파일러가 검사하는 것. sealed + switch가 Visitor를 대체한 근거.

모든 경우를 다뤘는지 컴파일러가 검사하는 것. 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허용 목록을 정하는 것이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 실무 적용 심화 — 프레임워크 소스로 읽는 패턴