76

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?

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
triad
  • 20,407
  • 13
  • 45
  • 50

12 Answers12

71

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)
Ekeko
  • 1,879
  • 14
  • 15
  • 12
    Be careful about copying lists this way. Your copy will be the same memory location, so changing one will affect both. A true deep copy will also copy the list items. See https://stackoverflow.com/q/51480079/1015595. – Big McLargeHuge Nov 11 '19 at 21:45
  • 3
    This shouldn't be the accepted answer. Copying like this is still just copying the reference of the list. Check my answer for detail – Muhammad Muzammil Nov 14 '19 at 06:12
  • 5
    @MuhammadMuzammil The question is not about how to implement a proper deep copy but "Could someone explain how exactly the copy method for Kotlin data classes work?" – Ekeko Nov 27 '19 at 09:00
  • 1
    @Ekeko I beg to differ. This is about a proper implementation of deep copy. See this "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?" – Muhammad Muzammil Nov 28 '19 at 05:53
14

Beware of those answers who are just copying list reference from an old object into the new one. One quick way (not very efficient, though) of deep copying is to serialize/deserialize objects i.e. convert the objects into JSON and then transform them back to POJO. If you are using GSON, here is a quick piece of code:

class Foo {
    fun deepCopy() : Foo {
        return Gson().fromJson(Gson().toJson(this), this.javaClass)
    }
}
Muhammad Muzammil
  • 1,313
  • 1
  • 12
  • 28
  • 1
    > **either serialize/deserialize objects or to convert [...] into JSON** so, you are implying like converting to/from JSON is something other than the serialization/deserialization itself. – ratijas Feb 07 '20 at 22:37
  • No, what I'm implying is that converting into JSON is a specific, and in some cases easiest, form of serializing. – Muhammad Muzammil Feb 10 '20 at 06:22
  • the best solution I found... Kudos @MuhammadMuzammil – Rahul Jul 02 '20 at 14:09
  • 25
    This is TERRIBLE, please don't do this. This is about a 1000 times slower and uses 1000 times more memory than a simple deepcopy function from e.g. apache commons. – Agoston Horvath Apr 20 '21 at 13:28
  • @MuhammadMuzammil Did you run a **performance test** in a loop to deep copy objects of a certain size, say 1kb, using the JSON serialization/deserialization vs the `ByteArrayOutputStream` serialization/deserialization vs the painful (to write) manual override of Kotlin `copy()`? – Paulo Merson Oct 25 '21 at 19:50
  • @PauloMerson No, I haven't. And I'm sure it's quite a heavy job. I did mention that this is a "quick" way of doing things although maybe not the best way. – Muhammad Muzammil Oct 26 '21 at 11:23
  • 1
    @MuhammadMuzammil That's a slippery word when unqualified. The proposed solution is quick (i.e., easy) *to implement* but probably not quick *to run*. – Paulo Merson Oct 26 '21 at 17:53
  • great answer! I got stuck on copying and changing value on another list I have mutableList inside of object, even I already create new object and reassign value of mutable list using items.addAll but it doesnot works thanks – rizkidzulkarnain Jan 20 '22 at 04:00
  • @AgostonHorvath how about you post an example of how to use `a simple deepcopy function from e.g. apache commons` rather than piling on a proposed and working solution? – zaitsman Apr 05 '22 at 04:50
  • @zaitsman it is not a working solution, as in it rides but would by far not pass DMV – Agoston Horvath Apr 06 '22 at 10:47
  • this solution is working for me so far but make sure there is no enum type in your field if your class has it and build using minifyEnable = true it will crash with error "noSuchFieldException" – rizkidzulkarnain Apr 07 '22 at 13:17
  • This solution works, is easy to understand, and implement. – Shimon Rothschild Sep 09 '22 at 04:46
12

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)
CrazyApple
  • 472
  • 2
  • 9
12

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

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
} 

Note: This solution should also be applicable in Android using the Parcelable interface instead of the Serializable. Parcelable is more efficient.

Dima Kozhevin
  • 3,602
  • 9
  • 39
  • 52
Sebas LG
  • 1,715
  • 1
  • 18
  • 31
  • 2
    Very nice.I think, it can be more useful with an extension like that : fun 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") return ois.readObject() as T } – Burak Dizlek Nov 22 '19 at 20:06
  • 1
    There must be something wrong with this implementation, why else Java/Kotlin team wouldn't add the same functionality? Or I'm missing some important info on this. – Farid Feb 19 '21 at 09:17
  • 2
    @Farid performance, this is not the most performant way to create a deep-copy. – Sebas LG Feb 19 '21 at 11:38
1

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.

Clyde
  • 7,389
  • 5
  • 31
  • 57
1

I face the same problem. Because in kotlin, ArrayList.map {it.copy} not copying all items of an object specially if a member is list of another object inside this.

The only solution, for deep copying of all items of an object I found on the web, is to serialize and deserialize the object when you send or assign it to a new variable. Code like as follows.

@Parcelize
data class Flights(

// data with different types including the list 
    
) : Parcelable

Before I receiving List of Flights, We can use JSON to deserialize the Object and serialize the object same time!!!.

First, we create two extension functions.

// deserialize method
fun flightListToString(list: ArrayList<Flights>): String {
    val type = object : TypeToken<ArrayList<Flights>>() {}.type
    return Gson().toJson(list, type)
}

// serialize method
fun toFlightList(string: String): List<Flights>? {
    val itemType = object : TypeToken<ArrayList<Flights>>() {}.type
    return Gson().fromJson<ArrayList<Flights>>(string, itemType)
}

We can use it like below.

   // here I assign list from Navigation args

    private lateinit var originalFlightList: List<Flights>
    ...
    val temporaryList = ArrayList(makeProposalFragmentArgs.selectedFlightList.asList())    
    originalFlightList = toFlightList(flightListToString(temporaryList))!! 

Later, I send this list to Recycler Adapter & there the content of the Flights object would be modified.

bindingView.imageViewReset.setOnClickListener {
        val temporaryList = ArrayList(makeProposalFragmentArgs.selectedFlightList.asList())
        val flightList = toFlightList(flightListToString(temporaryList))!!
        **adapter**.resetListToOriginal(flightList)
    }
Shihab Uddin
  • 6,699
  • 2
  • 59
  • 74
0

Maybe you can use kotlin reflection in some way here, this example is not recursive but should give the idea:

fun DataType.deepCopy() : DataType {
    val copy = DataType()

    for (m in this::class.members) {
        if (m is KProperty && m is KMutableProperty) {
            m.setter.call(copy, if (m.returnType::class.isData) {
                (m.getter.call(this) to m.returnType).copy()
            } else m.setter.call(copy, m.getter.call(this)))
        }
    }

    return copy
}
Treviño
  • 2,999
  • 3
  • 28
  • 23
0

If you use Jackson and not concerned about performance,then this simple extension function will give you this feature.

private val objectMapper = ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
    
fun <T> Any.copyDeep(): T {
        return objectMapper.readValue(objectMapper.writeValueAsString(this), this.javaClass) as T
    }
Mannie
  • 1,608
  • 1
  • 21
  • 33
0

Use this function:

private val gson = Gson()

fun <T> deepCopy(item: T?, clazz: Class<T>): T {
        val str = gson.toJson(item)
        return gson.fromJson(str, clazz)
    }
tohidmahmoudvand
  • 256
  • 2
  • 14
0

What you want is a deep copy. There are many tools available to do this.

  1. MapStruct: https://mapstruct.org/

Mapstruct generates code at compile time. Normally, it is to auto-generate mappers between java objects, but it also has a 'clone' functionality to create a deep copy of an object. Since this is generated code of what you'd manually write, it is the fastest way to achieve this.

There are many more (kryo, dozer, etc...), you can actually just google, for example here: https://programmer.group/performance-comparison-between-shallow-and-deep-copies.html

DO AVOID serialization-based 'clone': apache commons' SerializationUtils, jackson, gson, etc... They have a huge overhead, since it first creates a middle state. They are about 10-100 times slower than actual copying.

Agoston Horvath
  • 781
  • 8
  • 13
0

In case you have body parameters they also won't be copied. Many programmers keep there methods like this:

data class Data(
    val id: Int,
    val images: List<Image> = emptyList()
)  {
    // Body parameters
    var onImageClick: () -> Unit = { }
    var onLikeClick: () -> Unit = { }
}

When you copy this object, you get a new object with empty (default) body parameters (onImageClick and onLikeClick). To avoid this situation, just add a new method. Note that I use apply:

data class Data(
    val id: Int,
    val images: List<Image> = emptyList()
)  {
    var onImageClick: () -> Unit = { }
    var onLikeClick: () -> Unit = { }

    fun deepCopy(
        id: Int = this.id,
        images: List<Image> = this.images,
        onImageClickAction: () -> Unit = this.onImageClick,
        onLikeClickAction: () -> Unit = this.onLikeClick
    ) = Data(id = id,
            // Use deep copy of the list from above answers instead
            images = images).apply {
        onImageClick = onImageClickAction
        onLikeClick = onLikeClickAction
    }
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
0

Here is an approach that uses Kotlin reflection and extension functions to call copy() on members. The syntax isn't as clean as copy() but close.

If we have these data classes:

data class Foo(
    val name: String,
)
data class Bar(
    val id: Long,
    val foo: Foo,
)

then the code looks like this:

val template = Bar(...)
val copy = template.copy(id = 1)
    .copyFoo(Foo::name to "new name")

The extension function:

fun Bar.copyFoo(
    vararg overrides: Pair<KProperty1<Foo, *>, Any>
) {
    val oldFoo = this.foo
    val newFoo = copyMember<T>(oldFoo, *overrides)
    return this.copy(
       foo = newFoo
    )
}

or compact:

fun Bar.copyFoo(
    vararg overrides: Pair<KProperty1<Foo, *>, Any>
) {
    return this.copy(
       foo = copyMember<T>(this.foo, *overrides)
    )
}

The generic copyMember function looks like this.

/**
 * Dynamically copy a non-primitive member.
 *
 * Duplicate overrides are merged by keeping the last one.
 */
inline fun <reified T> copyMember(
    member: T,
    // The type T makes sure that all properties are members of the same type
    vararg overrides: Pair<KProperty1<T, *>, Any>
): T {
    val lookup = overrides.associateBy(
        { it.first.name },
        { it.second }
    )
    // Find the copy function of the member type
    val copyFn = T::class.memberFunctions.single { it.name == "copy" }
    // The copy function has an additional hidden parameter which contains "this" during the copy operation
    val instanceParam = copyFn.instanceParameter!!
    // These are the usual parameters for copy()
    val overrideParameters = copyFn.parameters
        .filter {
            lookup.containsKey(it.name)
        }
        .map {
            it to lookup[it.name]
        }
    val parameters = (listOf(instanceParam to member) + overrideParameters)
        .toMap()

    // Call copy with the instance and the overrides
    return copyFn.callBy(parameters) as T
}

The code relies a lot on type inference and internal checks in copy() to make sure the output is valid.

It's faster than serializing the classes but needs more code.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820