Какие ограничения на наследников sealed классов в Kotlin и где они должны находиться
Что проверяют
Знаете ли вы актуальные правила, а не формулировку из старых статей про «один файл», и понимаете ли, зачем эти ограничения нужны. Следом обычно спрашивают, чем sealed class отличается от enum и что будет с when, если добавить нового наследника.
Как отвечать
Правила действуют с Kotlin 1.5:
- пакет и модуль: прямые наследники sealed-класса или sealed-интерфейса объявляются в том же модуле и в том же пакете, файлы могут быть разными. Правило «только в одном файле» действовало до 1.5;
- вид наследника: класс верхнего уровня или вложенный в именованный класс, объект или интерфейс. Локальные классы и анонимные объекты наследниками быть не могут;
- только прямые наследники: ограничение касается первого уровня. Если наследник объявлен
open, его можно расширять где угодно; - конструкторы: sealed-класс абстрактный, экземпляр создать нельзя, а конструкторы бывают только
protected(по умолчанию) илиprivate.
Смысл в том, что компилятор знает полный список подтипов. Поэтому when по sealed-типу проверяется на полноту без ветки else, а с Kotlin 1.7 это обязательно и для when-оператора, не только для выражения. Добавили наследника, и все непокрытые when перестали компилироваться.
В Android так обычно описывают состояние экрана:
sealed interface UiState {
data object Loading : UiState
data class Content(val items: List<Item>) : UiState
data class Error(val message: String) : UiState
}data object (стабилен с Kotlin 1.9) даёт читаемый toString(), что удобно в логах.
Где спотыкаются
Пишут else в when по sealed-типу. Это отключает главную пользу: новый наследник молча попадёт в ветку по умолчанию.
Путают с enum. Константы enum это фиксированный набор единственных экземпляров, а наследники sealed-класса несут свои поля и создаются сколько угодно раз.
Не учитывают модули. Вынести наследника в соседний Gradle-модуль, например :feature, не получится.