ПрограммированиеKotlin разработчик

Как работают Data Objects (data object) в Kotlin, для чего они нужны, как реализованы equals/hashCode/toString, и чем отличаются от обычных object и data class?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса:

До Kotlin 1.9 object-объекты не могли быть data — вы не могли иметь синглтон, автоматически получающий equals, hashCode, toString, как data-классы. С появлением data object это ограничение снято. Теперь можно создать singleton-объект с авто-генерированными методами, пригодный для паттернов Value и Enum-like.

Проблема:

Ранее для получения корректного equals(), hashCode(), toString() даже у singleton-объектов приходилось вручную их реализовывать или использовать другие финты, что увеличивало BOILERPLATE и возможность ошибки.

Решение:

Используйте data object для объектов, чей инстанс уникален, и при этом необходимо стандартное поведение equals/hashCode/toString для передачи в коллекции, сериализации, сравнения и дебага.

Пример кода:

data object NotAvailable fun checkStatus(status: Any) = when (status) { NotAvailable -> "Data is missing" else -> "Other status" } val set = setOf(NotAvailable) println(NotAvailable in set) // true println(NotAvailable.toString()) // NotAvailable

Ключевые особенности:

  • Data object реализует equals/hashCode/toString по контракту data class
  • Это singleton-объект (единственный инстанс)
  • Подходит для value-like либо enum-like паттернов без данных

Вопросы с подвохом.

Могут ли data object содержать свойства?

Могут, но только val-свойства без backing field (т.к. ничего хранимого у singleton быть не должно)

data object Loading { val status: String get() = "Loading..." }

Чем data object отличается от обычного object с точки зрения equals?

Equals у обычного object проверяет только ссылочную идентичность, у data object сравнивается контракт data, но в случае singleton это всегда тот же объект. Однако, переопределённый equals/hashCode полезнее для коллекций.

Можно ли сделать наследование от data object?

Нет, data object финален, как и любой object в Kotlin — наследоваться нельзя.

Типовые ошибки и анти-паттерны

  • Использовать data object вместо enum, когда объектов несколько
  • Использовать data object, пытаясь хранить данные — не допускается
  • Не учитывать, что равенство всегда ссылочное, но предусматривается коллекциями по значению

Пример из жизни

Негативный кейс

Вместо enum для всех состояний использовали разные data object. Через год понадобилась сериализация по строкам, стали вручную сопоставлять имена объектов с типами.

Плюсы:

  • Лёгкая инициализация

Минусы:

  • Лишние костыли для сериализации, ошибка в сопоставлении

Позитивный кейс

Для возвращаемого значения сетевого запроса использовали data object для специальных статусов: Loading, Empty, Error. Так код компактен, поддержка equals, hashCode, toString автоматом.

Плюсы:

  • Удобно для проверки в коллекциях
  • Красивое логирование

Минусы:

  • Нельзя добавить переменные свойства, только val-lazy