web-dev-qa-db-ja.com

Kotlinデータクラスコピーメソッドがすべてのメンバーをディープコピーしない

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])

しかし、私は何かが欠けていますか、これらのメンバーがディープコピーを強制する必要があることを指定する必要なく、これを行うためのより良い方法がありますか?

12
triad

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)
30
Ekeko

@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)
_
4
CrazyApple

Kotlin(およびJava)でオブジェクトのディープコピーを作成する方法があります:それをメモリにシリアライズし、次にそれを新しいオブジェクトにデシリアライズします 。これは、オブジェクトに含まれるすべてのデータがプリミティブであるか、Serializableインターフェイスを実装する場合にのみ機能します

サンプルのKotlinコードを使用した説明 https://rosettacode.org/wiki/Deepcopy#Kotlin

注:このソリューションは、Android SerializableではなくParcelableインターフェースを使用する場合にも適用可能です。Parcelableはより効率的です。

2
Sebas LG

以前の答えに基づいて、ややエレガントではないが簡単な解決策は 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))
    }

これは手書きのコピー機能ほど高速ではありませんが、オブジェクトが変更されても更新する必要がないため、より堅牢です。

0
Clyde