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,新しいリストのコピー
ネストされたコレクションを持つdata classに対して.copy()を使用した場合、コレクションはコピーされますか?なぜですか?
いいえ、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フィールドや日付が正しくシリアル化されず、結果として不正な値とビジネスロジックのエラーが発生しました。
物語
Android開発向けのReduxのようなストアに状態を移行する際に、状態の木に対して
copy()を使用し、コピーの完全な独立性を期待しましたが、結局、一つのストアのブランチが他のものの状態を変更する原因となりました。同じ内部のmutableコレクションを参照しているからです。