Could someone explain how exactly the copy
method for Kotlin data classes work? It seems like for some members, a (deep) copy is not actually created and the references are still to the original.
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)
Output:
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)
Why is barCopy.x=0
(expected), but fooCopy.bar.x=2
(I would think it would be 0). Since Bar
is also a data class, I would expect foo.bar
to also be a copy when foo.copy()
is executed.
To deep copy all members, I can do something like this:
val fooCopy = foo.copy(bar = foo.bar.copy(), list = foo.list.toMutableList())
fooCopy: Foo(a=5, bar=Bar(x=0), list=[1, 2, 3])
But am I missing something or is there a better way to do this without needing to specify that these members need to force a deep copy?
The copy
method of Kotlin is not supposed to be a deep copy at all. As explained in the reference doc (https://kotlinlang.org/docs/reference/data-classes.html), for a class such as:
data class User(val name: String = "", val age: Int = 0)
the copy
implementation would be:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
So as you can see, it's a shallow copy. The implementations of copy
in your specific cases would be:
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)
As @Ekeko said, the default copy()
function implemented for data class is a shallow copy which looks like this:
fun copy(a: Int = this.a, bar: Bar = this.bar, list: MutableList<Int> = this.list)
To perform a deep copy, you have to override the copy()
function.
fun copy(a: Int = this.a, bar: Bar = this.bar.copy(), list: MutableList<Int> = this.list.toList()) = Foo(a, bar, list)
There is a way to make a deep copy of an object in Kotlin (and Java): serialize it to memory and then deserialize it back to a new object. This will only work if all the data contained in the object are either primitives or implement the Serializable interface
Here is an explanation with sample Kotlin code https://rosettacode.org/wiki/Deepcopy#Kotlin
Note: This solution should also be applicable in Android using the Parcelable interface instead of the Serializable. Parcelable is more efficient.
Building on a previous answer, an easy if somewhat inelegant solution is to use the kotlinx.serialization
facility. Add the plugin to build.gradle
as per the docs, then to make a deep copy of an object, annotate it with @Serializable
and add a copy method which converts the object to a serialised binary form, then back again. The new object will not reference any objects in the original.
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))
}
This won't be as fast as a handwritten copy function, but it does not require updating if the object is changed, so is more robust.
来源:https://stackoverflow.com/questions/47359496/kotlin-data-class-copy-method-not-deep-copying-all-members