일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- relay
- 프로그래머스
- textfield
- TEST
- 백준
- 코루틴
- Hilt
- androidStudio
- architecture
- Coroutines
- activity
- Compose
- MiTweet
- pandas
- 코드포스
- boj
- GitHub
- AWS
- Codeforces
- android
- 암호학
- ProGuard
- Kotlin
- Python
- MyVoca
- Gradle
- Rxjava
- livedata
- 쿠링
- Coroutine
- Today
- Total
이동식 저장소
Sealed class vs. enum class 본문
2년 전 글에서 sealed class와 enum class에 대해 간략하게 다룬 적이 있다.
[Kotlin] sealed class vs. enum class
Kotlin을 처음 배울 때는 enum class
와 sealed class
를 혼동하곤 했다. enum을 구현할 떄 enum class
대신 sealed class
를 써도 되지 않나? 결론 sealed class
를 써도 되지만, 바람직한 사용법은 아니다
thinking-face.tistory.com
위 글에서는 enumerated value를 구현하기 위해 sealed class
를 사용할 필요가 없다는 결론을 내렸다. 사실 당연하다. enum class
가 멀쩡히 존재하기 때문이다. 그러나 enum class
가 있음에도 이런 혼동을 하는 이유는 sealed class
와 enum class
가 실제로 매우 비슷하기 때문이다.
이 글에서는 sealed class와 enum class가 할 수 있는 일과 할 수 없는 일을 비교해 보고자 한다.
공통점
우선, sealed class와 enum class 모두 상속 관계를 제한한다. sealed class는 해당 클래스가 선언된 파일 안에서만 상속받을 수 있으며, 파일 외부에서는 sealed class를 상속받을 수 없다.
enum class는 애초에 final class이다.
또, sealed class와 enum class 모두 함수와 프로퍼티를 가질 수 있으며, 다른 클래스나 인터페이스를 상속받을 수 있다. 조금 특수한 형태이긴 하지만 둘 다 어쨌든 Kotlin 클래스이기 때문.
interface SomeInterface
enum class TestEnum(val order: Int) : SomeInterface {
VAL1(10),
VAL2(20),
VAL3(30);
fun action() {
println("Hello! I'm ${this.name}")
}
val orderReified: Int
get() = order * 10
}
sealed class SealedClass(val order: Int) : SomeInterface {
class SealedSubclass : SealedClass(100)
val reifiedOrder: Int
get() = order * 10
}
위 코드처럼 생성자 프로퍼티, getter 프로퍼티, 멤버 함수 등을 모두 선언할 수 있다. 다만 enum class에서는 변수나 함수 등을 enum 값 밑에(VAL1
, VAL2
, VAL3
밑에) 선언해야 한다. enum class 내부에는 enum 값이 가장 먼저 선언되어야 하기 때문.
차이점
그렇다면 sealed class와 enum class의 차이는 무엇인가?
우선, sealed class는 상속 관계를 제한하는 역할을 한다. 즉 sealed class의 하위 타입은 같은 파일에 선언된 서브 클래스로 한정할 수 있다. 그것도 무려 컴파일 시간에!
따라서 sealed class를 when
구문 등에서 사용할 때, 코드가 모든 case를 처리하고 있는지 컴파일 시간에 알 수 있으므로 일부 케이스를 빠트리는 등의 실수를 방지할 수 있다.
또, sealed class도 어쨌든 class이므로, 인스턴스를 생성할 수 있다. 이때 생성되는 인스턴스는 서로 다르다는 점이 중요하다. 왜냐하면 enum class의 value는 항상 동일한 객체를 반환하기 때문.
다음 테스트 코드를 보면 이해될 것이다.
class TestTest {
@Test
fun enumTest() {
val enum1 = TestEnum.VAL1
val enum2 = TestEnum.VAL1
println(enum1.hashCode())
println(enum2.hashCode())
assert(enum1 === enum2)
}
@Test
fun sealedTest() {
val sealed1 = SealedClass.SealedSubclass(100)
val sealed2 = SealedClass.SealedSubclass(100)
println(sealed1.hashCode())
println(sealed2.hashCode())
assert(sealed1 === sealed2)
}
}
enumTest
에서는 같은 enum 값의 identity가 동일함을 확인할 수 있다.
그러나 sealedTest
에서 생성한 sealed1
과 sealed2
는 서로 identical하지 않다. 일단 hashCode부터가 다르고, ===
연산으로 비교한 결과도 false
이다.
225344427
1604353554
Assertion failed
java.lang.AssertionError: Assertion failed
Sealed class는 동일한 특성을 갖는 객체를 여러 개 만들어낼 수 있는 반면, enum class는 선택지 자체만을 나타낸다고 정리할 수 있겠다. 요컨대 수저는 재료에 따라 금수저, 은수저 등이 존재할 수 있지만(sealed class), 월요일은 월요일 그 자체로 의미를 갖는다는 것(enum class).
객체의 의미를 가장 잘 드러낼 수 있는 방법을 고민해 보자.
Real case: navigation route는?
사실 이 글을 작성하게 된 이유는, Compose type-safe navigation에서 사용할 route를 sealed class와 enum class 중 무엇으로 만들어야 할 지 고민하고 있었기 때문이다. Sealed class와 enum class 모두 route를 표현할 수 있기 때문(상속을 제한하므로).
이 문제를 해결하려면, navigation argument의 속성을 정확하게 이해하고 있어야 한다.
공지 웹뷰 페이지로 가는 route를 NoticeWeb
클래스로 표현한다고 하자. NoticeWeb
에는 String 타입의 articleUrl
프로퍼티가 선언되어 있으며, 공지 웹뷰 페이지는 NoticeWeb.articleUrl
로 주어진 URL을 로드한다. 즉 서로 다른 articleUrl
를 갖는 NoticeWeb
객체가 존재할 수 있다는 뜻이다.
따라서 서로 다른 인스턴스를 만들 수 있는 sealed class를 사용하는 것이 적절하다. 매개변수가 없는 route도 통일성을 위해 sealed class로 표현하는 것이 좋다.
애초에 enum class를 못 쓴다
사실 Compose navigation type safe를 정확히 이해했다면 애초에 할 필요가 없는 고민이다. 왜냐면 type-safe route는 클래스를 매개변수로 받기 때문. (정확히는 reified T: Any
를 제네릭으로 받는다)
composable<TestSealedClass> {
// ...
}
composable<TestEnum> {
// ...
}
composable<TestEnum.VAL1> {
// 이게 되겠냐
}
그냥 sealed class/data class를 사용하는 게 옳다. 애초에 고민 자체가 잘못된...
뭐... 안그래도 sealed class와 enum class를 계속 헷갈리고 있었으니 이번 기회에 잘 정리했다고 생각해야겠다.
'Primary > Kotlin' 카테고리의 다른 글
kapt를 KSP로 migrate하기 (0) | 2024.06.08 |
---|---|
Kotlin 2.0.0 출시 (0) | 2024.06.08 |
mutableMapOf()의 내부 구현 (0) | 2024.02.03 |
[Kotlin] Coroutines Job (0) | 2022.11.10 |
[Kotlin] Dispatcher (0) | 2022.11.08 |