Почему нельзя наследоваться от дата класса в Kotlin
Что проверяют
Понимаете ли вы, что запрет вытекает из контракта equals, а не из прихоти языка.
Как отвечать
Точная формулировка: data class не может быть открытым или абстрактным, он всегда финальный, поэтому наследоваться от него нельзя. При этом сам он может наследовать обычный класс и реализовывать интерфейсы, ограничение одностороннее.
Причина в сгенерированных методах. Компилятор сам пишет equals, hashCode, toString, copy и componentN по свойствам основного конструктора. Если бы наследование было разрешено, эти методы стали бы некорректны:
- наследник добавляет свойство, а сгенерированный
equalsего не учитывает. Два объекта с разными значениями окажутся равны; - нарушается симметричность равенства: сравнение родителя с наследником и обратно дало бы разные результаты, а это прямое нарушение контракта
equals; copyобъявлен возвращающим тип родителя и физически создаёт объект родителя, теряя часть наследника. Это классическая проблема разрезания объекта;componentNдля деструктуризации тоже знает только свойства базового конструктора.
Ровно с той же проблемой борется Java, объявляя record финальным, и Scala, где наследование от case class тоже под запретом.
Что делать вместо этого: выносить общее в интерфейс и реализовывать его в каждом дата-классе; использовать композицию, вкладывая общую часть отдельным свойством; для замкнутой иерархии брать sealed interface или sealed class с дата-классами в качестве вариантов. Последнее и есть идиоматический способ описать состояние экрана.
Где спотыкаются
Считают запрет техническим ограничением компилятора.
Не могут объяснить, чем плох унаследованный copy.
Пытаются наследовать общие поля вместо интерфейса.
Забывают, что equals учитывает только свойства основного конструктора, и добавляют поля в тело класса.