Kotlinデータクラスのcopy
メソッドがどのように機能するかを誰かが説明できますか?一部のメンバーの場合、(深い)コピーは実際には作成されず、参照はまだ元のものであるようです。
_fun test() {
val bar = Bar(0)
val foo = Foo(5, bar, mutableListOf(1, 2, 3))
println("foo : $foo")
val barCopy = bar.copy()
val fooCopy = foo.copy()
foo.a = 10
bar.x = 2
foo.list.add(4)
println("foo : $foo")
println("fooCopy: $fooCopy")
println("barCopy: $barCopy")
}
data class Foo(var a: Int,
val bar: Bar,
val list: MutableList<Int> = mutableListOf())
data class Bar(var x: Int = 0)
_
出力:
foo:Foo(a = 5、bar = Bar(x = 0)、list = [1、2、3])
foo:Foo(a = 10、bar = Bar(x = 2)、list = [1、2、3、4])
fooCopy:Foo(a = 5、bar = Bar(x = 2)、list = [1、2、3、4])
barCopy:Bar(x = 0)
なぜ_barCopy.x=0
_(期待される)ですが、_fooCopy.bar.x=2
_(0になると思います)。 Bar
はデータクラスでもあるため、foo.copy()
が実行されると、_foo.bar
_もコピーになると予想されます。
すべてのメンバーをディープコピーするには、次のようにします。
_val fooCopy = foo.copy(bar = foo.bar.copy(), list = foo.list.toMutableList())
_
fooCopy:Foo(a = 5、bar = Bar(x = 0)、list = [1、2、3])
しかし、私は何かが欠けていますか、これらのメンバーがディープコピーを強制する必要があることを指定する必要なく、これを行うためのより良い方法がありますか?
Kotlinのcopy
メソッドは、ディープコピーではないはずです。リファレンスドキュメント( https://kotlinlang.org/docs/reference/data-classes.html )で説明されているように、次のようなクラスの場合:
data class User(val name: String = "", val age: Int = 0)
copy
実装は次のようになります。
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
ご覧のとおり、これは浅いコピーです。特定の場合のcopy
の実装は次のとおりです。
fun copy(a: Int = this.a, bar: Bar = this.bar, list: MutableList<Int> = this.list) = Foo(a, bar, list)
fun copy(x: Int = this.x) = Bar(x)
@Ekekoが言ったように、データクラスに実装されるデフォルトのcopy()
関数は、次のような浅いコピーです。
_fun copy(a: Int = this.a, bar: Bar = this.bar, list: MutableList<Int> = this.list)
_
ディープコピーを実行するには、copy()
関数をオーバーライドする必要があります。
_fun copy(a: Int = this.a, bar: Bar = this.bar.copy(), list: MutableList<Int> = this.list.toList()) = Foo(a, bar, list)
_
Kotlin(およびJava)でオブジェクトのディープコピーを作成する方法があります:それをメモリにシリアライズし、次にそれを新しいオブジェクトにデシリアライズします 。これは、オブジェクトに含まれるすべてのデータがプリミティブであるか、Serializableインターフェイスを実装する場合にのみ機能します
サンプルのKotlinコードを使用した説明 https://rosettacode.org/wiki/Deepcopy#Kotlin
注:このソリューションは、Android SerializableではなくParcelableインターフェースを使用する場合にも適用可能です。Parcelableはより効率的です。
以前の答えに基づいて、ややエレガントではないが簡単な解決策は kotlinx.serialization
機能を使用することです。ドキュメントに従ってbuild.gradle
にプラグインを追加し、オブジェクトのディープコピーを作成し、@Serializable
で注釈を付け、オブジェクトをシリアル化されたバイナリ形式に変換するコピーメソッドを追加してから、再度戻します。 。新しいオブジェクトは、元のオブジェクトを参照しません。
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor
@Serializable
data class DataClass(val yourData: Whatever, val yourList: List<Stuff>) {
var moreStuff: Map<String, String> = mapOf()
fun copy(): DataClass {
return Cbor.load(serializer(), Cbor.dump(serializer(), this))
}
これは手書きのコピー機能ほど高速ではありませんが、オブジェクトが変更されても更新する必要がないため、より堅牢です。