Kotlin数据类复制方法不能深度复制所有成员

tri*_*iad 16 copy deep-copy kotlin data-class

有人可以解释copyKotlin数据类的方法究竟是如何工作的吗?对于某些成员来说,实际上并没有创建(深层)副本,并且引用仍然是原始的.

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)
Run Code Online (Sandbox Code Playgroud)

输出:
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.barfoo.copy()执行时也是一个副本.

要深刻复制所有成员,我可以这样做:

val fooCopy = foo.copy(bar = foo.bar.copy(), list = foo.list.toMutableList())
Run Code Online (Sandbox Code Playgroud)

fooCopy:Foo(a = 5,bar = Bar(x = 0),list = [1,2,3])

但我是否遗漏了某些东西,或者是否有更好的方法来做到这一点,而无需指定这些成员需要强制进行深层复制?

Eke*_*eko 32

copyKotlin 的方法根本不应该是一个很深的副本.如参考文档(https://kotlinlang.org/docs/reference/data-classes.html)中所述,对于如下类的类:

data class User(val name: String = "", val age: Int = 0)
Run Code Online (Sandbox Code Playgroud)

copy实现将是:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
Run Code Online (Sandbox Code Playgroud)

所以你可以看到,这是一个浅薄的副本.的实现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)
Run Code Online (Sandbox Code Playgroud)

  • 以这种方式复制列表时要小心。您的副本将位于相同的内存位置,因此更改其中一个会影响两者。真正的深复制也会复制列表项。请参阅 /sf/ask/3603605561/。 (3认同)
  • 这不应该是公认的答案。像这样复制仍然只是复制列表的引用。详细看我的回答 (3认同)
  • @MuhammadMuzammil 问题不在于如何实现正确的深度复制,而是“有人能解释一下 Kotlin 数据类的复制方法到底是如何工作的吗?” (3认同)

Muh*_*mil 7

当心那些只是将列表引用从旧对象复制到新对象的答案。我发现的唯一快速深度复制方法是序列化/反序列化对象,即将对象转换为 JSON,然后将它们转换回 POJO。如果您使用的是 GSON,这里是一段代码:

class Foo {
    fun deepCopy() : Foo {
        return Gson().fromJson(Gson().toJson(this), this.javaClass)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这太可怕了,请不要这样做。与来自 apache commons 的简单深度复制函数相比,这大约慢 1000 倍,并且使用的内存多 1000 倍。 (4认同)
  • @MuhammadMuzmmil 当不合格时,这是一个狡猾的词。所提出的解决方案可以快速(即容易)*实施*,但可能不快速*运行*。 (2认同)

小智 5

正如@Ekeko所说,copy()为数据类实现的默认函数是一个浅表副本,看起来像这样:

fun copy(a: Int = this.a, bar: Bar = this.bar, list: MutableList<Int> = this.list)
Run Code Online (Sandbox Code Playgroud)

要执行深层复制,您必须覆盖该copy()功能。

fun copy(a: Int = this.a, bar: Bar = this.bar.copy(), list: MutableList<Int> = this.list.toList()) = Foo(a, bar, list)
Run Code Online (Sandbox Code Playgroud)


Seb*_* LG 5

有一种方法可以在Kotlin(和Java)中复制对象的深层副本:将其序列化到内存中,然后反序列化回新的对象。仅当对象中包含的所有数据都是原语或实现Serializable接口时,这才起作用

这是示例Kotlin代码的说明https://rosettacode.org/wiki/Deepcopy#Kotlin

import java.io.Serializable
import java.io.ByteArrayOutputStream
import java.io.ByteArrayInputStream
import java.io.ObjectOutputStream
import java.io.ObjectInputStream

fun <T : Serializable> deepCopy(obj: T?): T? {
    if (obj == null) return null
    val baos = ByteArrayOutputStream()
    val oos  = ObjectOutputStream(baos)
    oos.writeObject(obj)
    oos.close()
    val bais = ByteArrayInputStream(baos.toByteArray())
    val ois  = ObjectInputStream(bais)
    @Suppress("unchecked_cast")
    return ois.readObject() as T
} 
Run Code Online (Sandbox Code Playgroud)

注意:此解决方案也应适用于使用Parcelable接口而非Serializable接口的Android。可打包的效率更高。

  • 非常好。我认为,使用这样的扩展会更有用: fun &lt;T:Serialized?&gt;T.deepCopy(): T? { if (this == null) return null val baos = ByteArrayOutputStream() val oos = ObjectOutputStream(baos) oos.writeObject(this) oos.close() val bais = ByteArrayInputStream(baos.toByteArray()) val ois = ObjectInputStream( bais) @Suppress("unchecked_cast") 返回 ois.readObject() as T } (2认同)
  • @Farid 性能,这不是创建深度复制的最高效的方法。 (2认同)