sealed class 개념 정리
일반적인 부모 class를 자식 class에서 상속할 경우, 컴파일러는 해당 부모 class를 상속 받은 자식 class가 누구누구인지 알 수 없다.
예를 들어, 아래와 같이 결과값(Result)을 클래스로 만들면, 아래와 같이 성공, 실패 각 종류에 따라 클래스가 생성될 수 있다.
abstract class Result
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
이때, 각 Result별로 상태 메시지를 얻고 싶다고 하면, 아래와 같이 함수를 작성할 수 있는데 이때, when절에 Result를 직접 넣어도 else 구문을 넣지 않으면 compile error가 발생한다.

이렇게 에러가 발생하는 이유는 컴파일러가 Result를 상속 받는 클래스가 무엇이 있는지 판단할 수 없기 때문이다.
아래처럼 else 구문을 넣어 컴파일 에러를 해결할 수도 있지만, else를 추가한 순간 kotlin이 제공하는 when절의 장점이 사라진다.
- Result를 상속하는 새로운 클래스(Pass)가 생기고, when절에 해당 자식 클래스 분기를 넣지 않아도 오류가 나지 않아 디버깅 힘듬
fun getMessage(result: Result): String {
return when (result) {
is Success -> "Success with data: ${result.data}"
is Failure -> "Failure with error: ${result.error.message}"
else -> "컴파일러는 Result를 상속받는 클래스의 종류를 알 수 없음"
// is Pass 분기 작성하지 않아도 컴파일 오류 안남
}
}
data class Pass() : Result()
위와 같은 문제를 해결하기 위해 나온 class가 sealed class다.
sealed class 란?
sealed class란 봉인된 클래스라는 뜻으로 상속 가능한 하위 타입을 컴파일 타임에 제한하는 클래스다.
앞선 예시를 sealed class로 작성하면 아래와 같고, when에 else를 넣지 않아도 컴파일 에러가 발생하지 않는 것을 확인할 수 있다.
sealed class Result
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
fun getMessage(result: Result): String {
return when (result) {
is Success -> "Success with data: ${result.data}"
is Failure -> "Failure with error: ${result.error.message}"
}
}

sealed class 상속 받는 방법
sealed class를 상속 받는 방법에는 class(data class 포함)와 object가 있다.
아래 코드는 org.jetbrains.kotlin.utils 하위에 있는 코드 일부이며, sealed class를 상속 받는 object와 class가 모두 존재하는 것을 확인할 수 있다.

sealed class 상속 받는 방식은 아래 기준으로 진행하면 된다.
- 상태(변수)가 있거나 equals를 override할 경우에만 class로 상속 받는다.
- 이때, data class를 활용하면 equals를 override할 필요 없이 data class가 알아서 equals를 구현해준다.
- 그 외에는 메모리 절약을 위해 object를 이용한다.
sealed class를 언제 쓰면 좋을까? (간단 사례)
enum class FailStatus {
FAIL_REASON1,
FAIL_REASON2,
}
fun validateStatus(): FailStatus? { ... }
validateStatus()?.let {
throw BusinessException("검증에 실패했습니다")
}
보통은 null이 정상을 의미하지 않는데, 아래 코드에서는 null이 들어와야 검증이 통과한 상황이라 일반적인 의미와 다르게 사용되었다. 추가로, ?.let은 값이 있으면 다음 구문을 처리하는 것으로 보통 쓰이는데 여기서는 예외를 던지다보니 일관성 없는 코드라고 생각했다.
이로 인해, sealed class를 활용해 검증 결과를 명확화하는 식으로 리팩토링을 진행했다.
sealed class ValidationResult {
data object Pass : ValidationResult()
data class Fail(val status: FailStatus) : ValidationResult()
}
fun validateStatus(): ValidationResult {
if (검증 성공) return ValidationResult.Pass
return ValidationResult.Fail(status)
}
when(validationStatus()) {
is ValidationResult.Pass -> ...
is ValidationResult.Fail -> throw BusinessException("검증에 실패했습니다")
}
참고 - data object는 object와 다른가요?
kotlin의 object와 data object간 핵심적인 차이는 toString(), equals(), hashCode() 메소드의 자동 구현 여부입니다.
단순히 말하면, data object는 data class가 일반 class에 제공하는 편의 기능을 object에도 적용한 것으로 볼 수 있습니다.
참고 자료
'PROGRAMMING LANGUAGE > KOTLIN' 카테고리의 다른 글
| [Kotlin] 1.6.20 → 1.9.25 업그레이드 (0) | 2025.10.25 |
|---|---|
| [Kotlin Coroutine] channel & select (0) | 2024.08.16 |
| [Effective Kotlin] 8장 효율적인 컬렉션 처리 (0) | 2024.08.10 |
| [Effective Kotlin] 7장 비용 줄이기 (0) | 2024.08.03 |
| [Effective Kotlin] 6장 클래스 설계 (0) | 2024.07.28 |