En Kotlin, shallow copy (copie superficielle) crée un nouvel objet, mais les objets imbriqués (par exemple, les éléments des collections ou les champs de référence) ne sont pas copiés, mais référencent les mêmes objets que l'instance d'origine. Deep copy (copie profonde) signifie copier récursivement tous les objets imbriqués (graphe d'objets), afin que l'original et la copie soient entièrement indépendants.
Il n'existe pas de moyen universel dans la bibliothèque standard de Kotlin pour effectuer une copie profonde, et il est nécessaire de l'implémenter manuellement, en particulier pour des structures complexes ou des graphes avec des cycles.
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, même référence
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, nouvelle copie de la liste
Si on utilise
.copy()pour un data class avec des collections imbriquées, les collections seront-elles copiées ? Pourquoi ?
Non, copy() crée une copie superficielle. Les collections et objets imbriqués resteront des références aux mêmes objets. Toute modification dans ces objets sera visible à la fois dans l'original et dans la copie, ce qui peut entraîner des bugs.
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] — l'original a été modifié !
Histoire
Dans un projet, on a sauvegardé l'état d'un objet via le
.copy()d'un data class, pensant obtenir un "instantané". En revenant à un ancien état, il s'est avéré que la liste imbriquée (mutableList) avait été modifiée dans toutes les copies en raison de la copie superficielle.
Histoire
Dans un système de documents, on a copié des entités via sérialisation/désérialisation avec Gson, s'attendant à une copie profonde, mais les champs nullable et les dates étaient mal sérialisés — le résultat était des valeurs incorrectes et des erreurs de logique métier.
Histoire
Lors de la migration d'état dans un store de type Redux pour le développement Android, on a commencé à utiliser
copy()pour des arbres d'état, s'attendant à une indépendance totale des copies. Au final, une branche du store pouvait muter l'état d'une autre en raison de références aux mêmes collections mutables internes.