Java의 sealed 클래스와 빠짐없는 패턴 매칭

Java sealed classes and exhaustive pattern matching

neilmadden.blog ▲ 133 댓글 102 hnisjafx40

요약

Java의 sealed 클래스를 공개 API에 쓰면 하위 타입 하나만 늘어도 사용하는 쪽 코드가 실행 중에 깨질 수 있습니다.

개발자 닐 매든이 블로그에 쓴 글입니다. Java 17에 들어온 sealed 클래스(허용할 하위 타입을 직접 나열하는 클래스나 인터페이스)가 패턴 매칭과 만날 때의 장점과 함정을 예제로 보여 줍니다.

왜 중요한가

  • sealed 타입을 외부에 공개한 라이브러리는 하위 타입을 하나만 더해도 메이저 버전을 올려야 하는 호환성 깨짐이 생깁니다.
  • 컴파일러의 빠짐없는 검사는 전부 함께 다시 컴파일할 때만 보장됩니다. 따로 배포되는 모듈 사이에서는 안전망이 되지 못합니다.
  • 미리보기 단계인 PEM 암호 객체 지원(JEP 524)이 이런 sealed 인터페이스를 공개 API로 내놓고 있어 실제 문제로 번질 수 있습니다.

핵심 내용

  • sealed 인터페이스와 record 두 개로 만든 예제에서 switch는 default 절 없이도 모든 경우를 다룬 것으로 인정받습니다.
  • 여기에 세 번째 하위 타입을 더하면 그 switch를 쓰는 코드는 다시 컴파일할 때 오류가 납니다.
  • 사용하는 쪽을 다시 컴파일하지 않고 실행하면 컴파일 오류 대신 실행 중에 MatchException이 발생합니다.
  • 저자는 sealed 타입이 한 컴포넌트 안의 내부 구현에는 잘 맞지만 공개 API에는 맞지 않는다고 결론지었습니다.
  • 이 개념은 Haskell이나 ML의 대수적 데이터 타입(정해진 몇 가지 경우 중 하나인 값)과 같다고 설명했습니다.

HN 반응

  • Java가 Kotlin을 따라잡는 중이라는 댓글에, Java 언어를 만드는 쪽 이용자는 언어를 작게 유지하려고 검증된 기능만 늦게 들인다고 반박했습니다.
  • TypeScript도 never 타입 보조 함수로 빠짐없는 검사를 할 수 있다는 의견이 이어졌고, 그건 패턴 매칭이 아니라 빠짐없는 switch일 뿐이라는 반론도 나왔습니다.

댓글

30개 표시 · 전체 102개
  1. shepherdjerred HN

    Java가 이렇게까지 발전한 게 정말 마음에 듭니다. 그래도 저는 프런트엔드 작업이 많고 스택 전체에서 같은 언어를 쓰고 싶어서 여전히 TypeScript가 더 좋습니다.

    그건 그렇고, TypeScript에서도 빠짐없는(exhaustive) 패턴 매칭을 할 수 있어요! https://github.com/gvergnaud/ts-pattern

  2. ngruhn HN
  3. whilenot-dev HN

    맞습니다. 파이썬에서는 이 작은 헬퍼를 assert_never0라고 부릅니다. 저도 얼마 전에 이걸 TypeScript 문서에 추가하자는 PR을 올렸습니다: https://github.com/microsoft/TypeScript-Website/pull/3599

  4. n_e HN

    그 예제에서는 default 절도, 헬퍼도 필요 없습니다(다만 그러면 오류 메시지는 더 별로입니다).

  5. ddosmax556 HN

    그냥 이렇게 하면 돼요.

        default:
            matched satisfies never
            throw new Error(...)
    

    함수조차 필요 없다고요.

  6. wk_end HN

    이건 라이브러리가 필요 없습니다. 관용적인 방법은 TypeScript의 타입 좁히기(narrowing)와 기본 제어 흐름(if와 switch, 경우에 따라 IIFE)에 기대고, 빠짐없는 처리를 강제하려면 (필요하면) never 타입을 함께 쓰는 것입니다.

    https://www.typescriptlang.org/play/?jsx=4#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wG4AocmATzCTgBEUYU4BecuOAHzgG84NOgC4CMJAA8YZOGggA7cYtEBnGFGDyA5nAC+nHv0G0ko-KC0yVUNKvWaduilROIkKgK4AbeBy68BIVMCCABrGQATZhRRJhY9AwDjEQIkKBwoGTSM0QBRdOg9ZwikNC8UIjgANwq4Ik8fUWQGmGcAeja4ADkIcUEAC2YB4BU4JHlMaDR3MYlBjzVgKvH3UYBaOAAVEwBlNA0weDQUeUEkLy8x5ahqWRQVekHRgCMkcbhB+QivJAjyDrgJwiA3oAElQQAxXJwADuCnw8CQ1zqSBgHigpw8XyQmE0vwAdHByHJ5GoPjAQF4msQYPiAFI7AAa+NyPxA418cAAFFyAJTsAB8-AMXBUMOAMDQ-S59W8tKC-L4Iq4KuOD1SBUyoiIaIxcAAPGABQB5CBgFQAQjgAEFTtlChA0Gh0b99W0jRQVV67ur8GF8KpxZL+tzZT58VEWPiFcLvXGffQEZJpNrUejToaBXww7TIyh8SSlDBdG6Pcr4wmCBYAyjdRmLHBrGg2Nn3HKI9F8U3dHA2gLPfH9F79LpeXzSEA

    라이브러리 쪽이 조금 더 낫다고 할 수는 있겠지만, 외부 의존성과 늘어나는 복잡도를 감수할 만한 가치는 없다고 봅니다.

  7. shepherdjerred HN

    바닐라 TypeScript로 하는 얘기가 아니라고 미리 단서를 달았어야 했네요.

    맞아요, switch 안에서 assert never로 할 수 있습니다. 하지만 그건 패턴 매칭이 아닙니다. 컴파일 타임에 강제되는 빠짐없는 switch일 뿐이에요.

    그것도 좋긴 한데, 거듭 말하지만 패턴 매칭은 아닙니다.

  8. armchairhacker HN

    Java에는 재귀 패턴도 있고, (아직 프리뷰 단계일 수 있지만) 리터럴 패턴도 있습니다. switch (x) { case A(B(true, var c)) -> f(c); }

  9. another_twist HN

    sealed에 익숙해지는 데 시간이 좀 걸렸고, 처음에는 어디에 쓰는지도 잘 몰랐습니다. 그런데 AI가 Java에서 이걸 엄청 좋아하는 것 같아서 써 봤더니 이제야 이해가 되네요. 특히 DI를 쓰면서 일부를 deprecated 처리하고 싶을 때, 정말 좋은 접근 제어 수단 같습니다. 물론 패턴 매칭도 빼놓을 수 없고요. 페이로드 형태가 바뀌는 이벤트 기반 메시지에서 아주 유용합니다.

  10. timshel2 HN

    지금쯤 Scala는 주먹을 불끈 쥐고 있겠네요.

  11. hibikir HN

    최근 몇 년간 Java에 들어온 기능의 80%는 문법이 더 못한 Scala 기능입니다. 더 나은 문법은 하위 호환이 안 되기 때문이죠. Scala 3가 업계에서 채택되는 데는 좋지 않은 일인데, 마틴(Martin)은 이를 돕는 데 전혀 관심이 없다고 봅니다. 어쩌다 보니 몇몇 회사가 프로덕션에 쓸 만큼 좋은 기능을 갖춘 자기 연구용 언어로 만족하고 있는 거죠. 산업 현장에서의 쓰임을 신경 쓰는 언어는 조직 운영 방식부터 다릅니다.

  12. mlinksva HN

    저는 어느 쪽과도 관계가 없지만, Java와 Scala가 서로 맞서는 사이는 아니라고 보고, 둘 다 잘 지내고 있다고 생각합니다. 말할 것도 없이 Java는 기대 이상으로 계속 발전해 왔고, 세상에서 차지하는 비중이 워낙 크니 소비자 후생으로 따지면 [손짓으로 어마어마하게 큰 숫자] 만큼의 가치가 있습니다. Scala도 지난 몇 년간 발전해서 다시 한번 중대형 애플리케이션 언어 중 가장 흥미로운 언어가 되었다고 봅니다. 예를 들어 최근 오더스키(Odersky)의 https://scaladays.org/session/opening-session-keynote/ 와 https://dl.acm.org/doi/10.1145/3786335.3813127 을 보세요.

  13. AdrenalinMd HN

    저는 아직도 Scala로 코딩합니다. 하지만 얼마나 더 할지는 모르겠네요. 정말이지, Scala로 코드를 쓰는 건 즐거움 그 자체입니다.

    프랑스 시 같아요.. 우아함이 넘치죠. 게다가 이펙트 시스템 같은 헛소리도 없고요. 마틴 오더스키가 의도한 그대로입니다!

  14. rr808 HN

    맞아요, 프랑스 시 같긴 하죠. 좋아하는 사람이 별로 없다는 점에서요. 저는 지난 2년간 Spark 프로젝트를 했는데 코드베이스가 끔찍합니다. 팀원 모두 싫어해서 내년에는 PySpark로 옮길 거예요. Scala는 죽었습니다.

  15. rhdunn HN

    저는 Scala의 많은 부분이 좋지만 implicit은 싫습니다. C/C++의 암묵적 타입 변환과 비슷해서 잘못된 추상화라는 느낌이 들어요. 코드가 무엇을 하는지 추론하기도, 호출되는 타입이나 메서드로 이동하기도 어려워지니까요.

    Scala의 implicit이 하는 일은 확장 메서드(extension method)로 하는 쪽이 더 좋습니다 [1], [2]. 확장 메서드로 부족한 부분은 타입을 확장해 Comparable 같은 인터페이스를 구현하게 만들 수 없다는 점뿐인데, 익명 객체를 반환하는 확장 메서드를 만들면 됩니다 [3].

    [1] https://kotlinlang.org/docs/extensions.html#anonymous-extension-functions

    [2] https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

    [3] https://discuss.kotlinlang.org/t/extensions-function-to-implement-interface/411

  16. theanonymousone HN

    Scala로 코드를 쓰는 건 즐거움 그 자체입니다.

    맞아요, 근데 읽을 때는 좀 고문이죠..

  17. panny HN

    sealed 타입이면 그 switch default들은 필요 없을 것 같은데요. 컴파일러로 확인해 보진 않았지만, 불필요하다고 생각합니다.

  18. thfuran HN

    default가 있는 switch들은 둘 다 리터럴 case만 있는 String을 대상으로 switch합니다. 그건 default 없이는 빠짐없이 처리할 수 없고, switch 표현식은 반드시 빠짐없이 처리해야 합니다.

  19. Larrikin HN

    Java는 계속 Kotlin을 따라잡으려 하네요. https://kotlinlang.org/docs/sealed-classes.html

    Java를 C++, Ruby, Python과 비교하던 시절에는 Java도 나쁘지 않았습니다. 하지만 Scala(무분별하게 다 집어넣다가 실패한 것 같은데)를 거치고 특히 Kotlin을 써 본 뒤로는 돌아갈 이유를 못 찾겠어요.

  20. pron HN

    저보다 가구가 훨씬 많은 수집광이 있다고 해서, 제가 소파를 하나 산다고 그 수집광(소파가 다섯 개나 있는)을 따라잡으려는 건 아니잖아요. Java는 따라잡으려는 게 아닙니다. 우리는 Java가 Kotlin의 모든 기능을 갖기를 원하지 않기 때문이고, 오히려 그렇게 되지 않으려고 아주 열심히 노력하고 있습니다(게다가 우리는 아직 Kotlin 기능을 단 하나도 가져온 적이 없습니다. Kotlin, Java, 그 밖의 많은 언어가 흔히 같은 영감, 대개는 ML 계열 언어에서 출발하긴 해도 우리 기능은 보통 다른 형태를 띱니다). 어떤 언어는 기능이 풍부하기를 목표로 하고, 어떤 언어는 더 최소한이기를 목표로 하기 때문입니다. 둘 다 시간이 지나면 기능이 쌓이겠지만 속도가 아주 다릅니다(우리는 기능의 가치가 언어에 더해지는 복잡도를 정당화한다고 만족할 때까지 몇 년이고 미루는데, 복잡도를 비교적 낮게 유지하는 것이 우리에게 매우 중요하기 때문입니다).

    더 풍부한 언어를 원하신다면 Java 플랫폼에는 선택지가 있습니다. 하지만 많은 사람은 더 작은 언어를 선호하고(Java 플랫폼 개발자 중 Java 언어를 쓰는 비율은 지난 15년 넘게 거의 일정하게 ~90%를 유지해 왔습니다), 한편으로 상당수의 개발자가 더 풍부한 언어를 선호한다는 것도 알고 있어서 그분들도 배려하고 있습니다. 어느 쪽이든 JDK의 95%는 런타임(표준 라이브러리와 JVM)과 도구이고, 어떤 프런트엔드 언어를 선호하든 이점은 누리실 수 있습니다.

  21. fallingbananna HN

    비유가 아주 적절하네요. 다만 덧붙이고 싶은 건, Kotlin은 Scala처럼 기능을 쟁여 두고 있지는 않다는 겁니다.

    Scala, C++, 심지어 TypeScript나 C#과 비교해도 Kotlin은 잘 설계된 최소한의 현대 언어로 느껴집니다(널 가능성 검사나 데이터 클래스처럼 우리가 당연하게 기대하게 된 편의 기능을 제공하면서 가능한 한 최소한이라는 뜻입니다).

    (*코루틴은 예외입니다. 이 부분은 Java가 가상 스레드와 구조적 동시성으로 Kotlin보다 훨씬 더 잘 해내고 있다고 생각합니다. Kotlin은 "영원히 실험적인" API인 데다 풋건(footgun)투성이이고 관측 가능성도 전혀 없으니까요.)

  22. gf000 HN

    Kotlin은 엄청나게 거대하고, 더 복잡한 기능 하나 대신 작은 기능 n개를 자주 추가하기 때문에 Scala와 비교하는 건 맞지 않는다고 봅니다.

    재미있게도 Kotlin이 when이라는 서둘러 내놓은 문법적 설탕을 추가했는데, Java가 와서 대수적 데이터 타입과 빠짐없는 처리 검사를 갖춘 제대로 된 패턴 매칭을 플랫폼 자체에 넣어 버렸습니다. 게스트 언어의 주특기에서 오히려 더 나은 걸 내놓은 셈이죠. 한편 Scala에서는 이런 "랜딩 페이지가 계속 늘어나는" 식의 사고방식을 느껴 본 적이 없습니다.

  23. derriz HN

    물론 주관적이긴 하지만, 제게 Kotlin은 결코 최소한의 언어가 아닙니다. 기능 면에서는 Java보다 C#에 더 가깝다고 봅니다. TypeScript와 Java가 훨씬 작은 언어처럼 느껴져요. 물론 반(反)미니멀리즘 면에서는 C++가 독보적이고요.

  24. speed_spread HN

    저는 Kotlin이 전반적으로 풋건투성이라고 생각합니다. "안 될 게 뭐 있어?" 하는 식으로 이것저것 다 허용하거든요. Kotlin 설계자들이 뽐내고 싶었을 뿐, 그 결과로 나오는 코드 패턴 상당수가 끔찍할 거라는 점은 고려하지 않은 느낌이에요.

  25. CrimsonRain HN

    Java 기능 구성을 아주 전문적으로 이끌고 계시네요. 이러니 불평하기도 어렵습니다.

    기술적으로 대단한 일은 아니고 우선순위도 높지 않다는 건 압니다만, 제가 다르게 바라는 건 흔한 보일러플레이트나 동작, 패턴을 간단한 한 줄로 줄여 주는 문법적 설탕이 훨씬 더 많았으면 하는 것 하나뿐입니다.

    그런데 이제 코드 대부분을 AI가 쓰다 보니, 그게 얼마나 중요한지 이제는 의문이 듭니다.

  26. bmacho HN

    둘 다 시간이 지나면 기능이 쌓이겠지만 속도가 아주 다릅니다(우리는 기능의 가치가 언어에 더해지는 복잡도를 정당화한다고 만족할 때까지 몇 년이고 미루는데, 복잡도를 비교적 낮게 유지하는 것이 우리에게 매우 중요하기 때문입니다).

    이 전략으로도 결국 Java는 임의의 지역 최적해로 흘러갈 겁니다. 하위 호환성을 중시하는 다른 언어들이 그랬던 것처럼, 그저 더 느리게 갈 뿐이죠. 탐욕 알고리즘이 그렇습니다. 천천히 하고, 각 단계가 정말 언어를 개선하는지 각별히 신경 쓰더라도 마찬가지입니다.

  27. pron HN

    모든 것은 언젠가 끝이 나고, 그걸 피할 전략은 없습니다. 하지만 환경의 실제 변화에 적응하려 하지 않는 것은 그 끝을 앞당기는 지름길이 될 수 있겠죠. 그리고 네, 우리는 어떤 변경이든 충분히 그만한 가치가 있도록 정말로 노력합니다.

    그런데 2026년 현재 Java는 전 세계 전체로 보면 세 번째 아니면 두 번째로 인기 있는 언어이고, 성능에 민감하거나 유지보수성이 필요한 대규모 애플리케이션에서는 1순위 선택지입니다. 그럼에도 우리의 최선의 노력에도 불구하고 20년 뒤에 Java가 급격히 쇠퇴해서 지금의 Go 정도의 인기에 그치게 될 가능성은 있을까요? 물론 있습니다.

  28. valzam HN

    물론 '언어에는 두 종류가 있다. 사람들이 불평하는 언어와 아무도 쓰지 않는 언어'라는 말을 빌리자면, 언어 설계에도 두 종류가 있죠. 완벽하게 일관된 설계와 현실 세계와 부딪혀 본 설계요.

  29. oldsecondhand HN

    문제는 어떤 기능이 어떻게 오용될지 예측하기 어렵고, 해로운 기능을 언어에서 제거하기는 더 어렵다는 겁니다. 그래서 Java가 천천히 가는 거고요. (다만 문자열 보간에 대해서는 너무 머뭇거리는 게 아닌가 싶습니다. 하지만 타입 추론이 너무 많으면 코드가 읽기 더 어려워진다고도 생각하고요.)

  30. DarkNova6 HN

    저는 Java가 의도적으로 후발 주자를 자처하는 언어라는 점이 좋습니다. 최신 유행을 쫓지 않고, 다른 언어에서 검증된 기능이 무엇인지 살펴보거든요.

    Kotlin과 C#이 내세우는 약속에는 좀 덤덤해질 수밖에 없습니다. 겉으로는 훌륭해 보이지만, 더 복잡한 코드를 다루기 시작하면 금방 흠이 드러나곤 하죠.

    Java는 놀라울 만큼 일관되고 날카로운 모서리가 정말 없습니다. 제네릭과 타입 소거가 있어도, C#에서 언어 의미론과 씨름하는 일이 Java에서보다 훨씬 잦았습니다.

Hacker News에서 보기 ↗