Почему поля в data классах Kotlin лучше делать неизменяемыми
Что проверяют
Понимаете ли вы, что data-класс это носитель значения, и что сгенерированные для него equals, hashCode и copy рассчитаны именно на неизменяемые поля. Для мобильной разработки вопрос не теоретический: на неизменяемости держится обновление состояния экрана, и ошибка тут проявляется как «данные поменялись, а интерфейс нет».
Как отвечать
Три довода, от практики к общему.
Первый: hashCode считается по свойствам первичного конструктора. Объект положили ключом в HashMap или в HashSet, поменяли поле, объявленное как var, и он потерялся в своей же коллекции, потому что лежит в старой корзине.
Второй, главный для Android: обновление состояния строится на сравнении значений. Держатель состояния не оповещает подписчиков, если новое значение равно предыдущему, а функция экрана пропускает перерисовку по той же причине. Мутация поля внутри существующего экземпляра не меняет ни ссылку, ни результат сравнения, поэтому обновление просто не доезжает. С неизменяемыми полями вы обязаны создать новый объект, и это работает предсказуемо:
data class UiState(val items: List<Item>, val loading: Boolean)
state.value = state.value.copy(loading = false)Третий: общий объект нельзя незаметно испортить из другого потока, неизменяемое значение читается без синхронизации и спокойно отдаётся в список и в подсчёт различий.
Оговоритесь, что val запрещает переприсваивание ссылки, но не делает структуру глубоко неизменяемой.
Где спотыкаются
Объявляют поле как val, а внутри держат изменяемый список и правят его на месте. Ссылка та же, сравнение говорит, что состояние не изменилось, подсчёт различий не находит правок, список на экране остаётся старым. Лечится неизменяемым типом коллекции и заменой списка целиком.
Второе: меняют var прямо в объекте состояния и потом часами ищут баг в библиотеке отображения. Библиотека работает правильно, ей просто не сообщили об изменении.
Третье: объясняют пользу неизменяемости оптимизациями компилятора и сборщика мусора. Настоящие причины другие: корректность сравнения и хеша, предсказуемость обновления и безопасность при работе из нескольких потоков.