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

Объясните различие между shallow copy и deep copy в Kotlin. Как реализовать глубокое копирование объектов, и в чем типичные ошибки при копировании сложных структур?

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

Ответ.

В Kotlin, shallow copy (поверхностное копирование) создает новый объект, но вложенные объекты (например, элементы коллекций или поля-ссылки) не копируются, а ссылаются на те же объекты, что и исходный экземпляр. Deep copy (глубокое копирование) означает рекурсивное копирование всех вложенных объектов (графа объектов), чтобы оригинал и копия были полностью независимы.

В стандартной библиотеке Kotlin нет универсального средства для глубокого копирования, и его необходимо реализовывать вручную, особенно для сложных структур или графов с циклами.

Пример поверхностного копирования:

data class Person(val name: String, val addresses: List<String>) val original = Person("Anna", listOf("Moscow", "London")) val shallow = original.copy() println(original.addresses === shallow.addresses) // true, одна и та же ссылка

Пример глубокого копирования:

data class Person(val name: String, val addresses: List<String>) { fun deepCopy(): Person = Person(name, addresses.toList()) } val deep = original.deepCopy() printfn(original.addresses === deep.addresses) // false, новая копия списка

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

Если использовать .copy() для data class с вложенными коллекциями, будут ли коллекции скопированы? Почему?

Ответ с примером:

Нет, copy() создает поверхностную копию. Вложенные коллекции и объекты останутся ссылками на те же объекты. Любые изменения в этих объектах будут видны и в оригинале, и в копии, что может привести к багам.

data class Box(val items: MutableList<String>) val box1 = Box(mutableListOf("A")) val box2 = box1.copy() box2.items.add("B") printfn(box1.items) // [A, B] — модифицировался и оригинал!

Примеры реальных ошибок из-за незнания тонкостей темы.


История

На проекте сохраняли состояние объекта через обычный .copy() data class, считая, что получают "снимок". При возврате к старому состоянию оказывалось, что вложенный список (mutableList) был изменен во всех копиях из-за поверхностного копирования.


История

В системе документов копировали сущности через сериализацию/десериализацию с помощью Gson, ожидая глубокое копирование, но nullable поля и даты сериализовались неправильно — в результате появлялись некорректные значения и ошибки бизнес-логики.


История

При миграции состояния в Redux-подобном store для Android-разработки стали использовать copy() для деревьев состояния, ожидая полную независимость копий. В итоге одна ветка store могла мутировать состояние другой из-за ссылок на одни и те же внутренние mutable-коллекции.