112

The Kotlin documentation describes cloning only in accessing Java and in enum class. In latter case clone is just throwing an exception.

So, how would I / should I clone arbitrary Kotlin object?

Should I just use clone() as in Java?

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Dims
  • 47,675
  • 117
  • 331
  • 600

10 Answers10

115

For a data class, you can use the compiler-generated copy() method. Note that it will perform a shallow copy.

To create a copy of a collection, use the toList() or toSet() methods, depending on the collection type you need. These methods always create a new copy of a collection; they also perform a shallow copy.

For other classes, there is no Kotlin-specific cloning solution. You can use .clone() if it suits your requirements, or build a different solution if it doesn't.

yole
  • 92,896
  • 20
  • 260
  • 197
  • How can I clone list? For example `List` – Dims Mar 01 '18 at 16:13
  • @Dims, you can do that just by using an extension on Iterable toList() or toMutableList(). – gromyk Jan 13 '20 at 10:47
  • 2
    @yole: Is it documented/guaranteed that `toSet()` returns a new instance? That does seem to be how it is currently implemented, but the [library docs](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/to-set.html) say only that it returns a Set. I'm a little hesitant to depend on this behavior if it isn't part of the library contract. – big_m Feb 18 '21 at 21:23
38

You can use Gson library to convert the original object to a String and then convert back that String to an actual Object type, and you'll have a clone. Although this is not the intended usage of the Gson library which is actually used to convert between JSON and other object types, but I have devised this method to solve the cloning problem in many of my Kotlin based Android applications. See my example. Put this function in the class/model of which you want to create a clone. In my example I'm cloning an Animal type object so I'll put it in the Animal class

class Animal{
 fun clone(): Animal 
 {
   val stringAnimal = Gson().toJson(this, Animal::class.java)
   return Gson().fromJson<Animal>(stringAnimal, Animal::class.java)
 }
}
       

Then use it like this:

val originalAnimal = Animal()
val clonedAnimal = originalAnimal.clone()
zulkarnain shah
  • 971
  • 9
  • 20
  • This is not a beautiful approach, but is the only it work for me. – Osvel Alvarez Jacomino Dec 16 '19 at 03:00
  • 1
    @OsvelAlvarezJacomino This will work for most of your use cases – zulkarnain shah Dec 16 '19 at 06:40
  • 1
    I know it will work for most of cases, but i don't like to add external libraries just to avoid shallow copies. In my case, I need to use Gson for other things in code, that's why I decided to used it. – Osvel Alvarez Jacomino Dec 16 '19 at 20:13
  • Ya true. Until we have a better inbuilt solution in Kotlin, this will do as a work-around – zulkarnain shah Dec 17 '19 at 14:52
  • Nice, approach for complex views – Jay Jun 16 '20 at 07:40
  • 2
    Oh god, please don't do this, ever. It comes with a *MASSIVE* overhead. – Agoston Horvath Dec 09 '20 at 13:29
  • @AgostonHorvath or anyone, 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 painful (to write) manual override of `clone()`? – Paulo Merson Oct 25 '21 at 20:09
  • 3
    @PauloMerson you can find a recent performance test here: https://www.baeldung.com/java-performance-mapping-frameworks ; gson would be about twice as slow as the slowest performer in that test, with having to do both serialization and deserialization. And yes, the overhead can be as high as 100x slower! – Agoston Horvath Oct 27 '21 at 07:37
  • Be very careful with this method, because JSON fields and Java classes are not always equivalent, or the serialization itself may make subtle changes to your data. E.g. encountered a situation just now where Gson removed milliseconds from Date objects. – Artemiy Feb 17 '22 at 16:35
  • Wouldn't be better to use something like https://github.com/EsotericSoftware/kryo library instead of json? At least should be more efficient – rascio Jun 05 '22 at 15:36
33

A Kotlin data class is easy to clone using .copy()

All values will be shallow copied, be sure to handle any list/array contents carefully.

A useful feature of .copy() is the ability to change any of the values at copy time. With this class:

data class MyData(
    val count: Int,
    val peanuts: Int?,
    val name: String
)
val data = MyData(1, null, "Monkey")

You could set values for any of the properties

val copy = data.copy(peanuts = 100, name = "Elephant")

The result in copy would have values (1, 100, "Elephant")

Gibolt
  • 42,564
  • 15
  • 187
  • 127
9

If the class you are trying to clone does not implement Cloneable or is not a data class and is a part of an outside library, you can create an extension method that returns a new instance. For example:

class Person {
  var id: String? = null
  var name: String? = null
} 
fun Person.clone(): Person {
  val person = Person()
  person.id = id
  person.name = name
  return person 
}
Horatio
  • 1,695
  • 1
  • 18
  • 27
6

It requires to implement Cloneable for your class then override clone() as a public like:

public override fun clone(): Any {<your_clone_code>}

https://discuss.kotlinlang.org/t/how-to-use-cloneable/2364/3

giang nguyen
  • 393
  • 4
  • 12
4
fun <T : Any> clone (obj: T): T {
  if (!obj::class.isData) {
    println(obj)
    throw Error("clone is only supported for data classes")
  }

  val copy = obj::class.memberFunctions.first { it.name == "copy" }
  val instanceParam = copy.instanceParameter!!
  return copy.callBy(mapOf(
    instanceParam to obj
  )) as T
}

Kirill Groshkov
  • 1,535
  • 1
  • 22
  • 23
  • 1
    I like this approach, but it doesn't seem to compile. I'm guessing that something changed in the Kotlin reflection API? I copy-pasted this verbatim into my code and it resulted in a compilation error: `Unresolved reference: memberFunctions` – Dave Yarwood Jan 22 '20 at 16:53
  • It looks like you have to add `org.jetbrains.kotlin:kotlin-reflect` to your dependencies in order to use reflection. However, I tried that and I still got the same error. I'm not sure if I'm doing something wrong or if something broke upstream. – Dave Yarwood Jan 22 '20 at 17:28
2

I've voted for @yole for nice answer, but other ways if you don't (or can't) use data class. You can write helper method like this:

object ModelHelper {

    inline fun <reified T : Serializable> mergeFields(from: T, to: T) {
        from::class.java.declaredFields.forEach { field ->
            val isLocked = field.isAccessible
            field.isAccessible = true
            field.set(to, field.get(from))
            field.isAccessible = isLocked
        }
    }

}

So you can "copy" instance A into B by:

val bInstance = AClassType()
ModelHelper.mergeFields(aInstance, bInstance)

Sometimes, I use this way to merge data from many instances into one object which value available (not null).

dphans
  • 1,543
  • 19
  • 20
  • Yes, sometimes we cannot use `data class`es, if make them `abstract` or `open`. How do you think, will this code compile in Android `release` build (it doesn't like reflection). – CoolMind May 06 '19 at 16:19
  • This is good, but has a bug. If a field is `null` in `from` it is not copied to `to` (therefore keeping the original, possibly not `null`, value). The fix is to remove the `if`. – acdcjunior May 17 '20 at 16:23
  • @acdcjunior Yes, you are right. I just remove the if statement. I use if statement because API issue in my teamates, sometime not response data sometime have data, so I catch all data if available and save into local database, if not, null will override data – dphans Mar 03 '21 at 07:45
  • Makes sense! Thanks for the fix! – acdcjunior Mar 03 '21 at 17:30
  • @CoolMind I applied for company projects, and there apps available on the playstore. But I think this way is not recommended because reflection. – dphans Mar 06 '21 at 01:49
1

Here is a consistent solution that works for any object type:

Kotlin's Array data structure provides a clone() method that can be used to clone the contents of the array:

val a = arrayOf(1)
//Prints one object reference
println(a)     
//Prints a different object reference
println(a.clone())

As of Kotlin 1.3, the clone method has been supported on all major targets, so it should be usable across platforms.

Ethan Hsu
  • 11
  • 1
  • 1
0

It's also possible to clone an object using kotlinx.serialization

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

@Serializable
class A
{
    val name: String = "Cloneable class A"

    fun clone(): A {
        val json = Json(JsonConfiguration.Stable)
        val jsonStr = json.stringify(serializer(), this)
        return json.parse(serializer(), jsonStr)
    }
}
Sergey Stasishin
  • 291
  • 2
  • 10
0

Collection copying functions, such as toList(), toMutableList(), toSet() and others, create a snapshot of a collection at a specific moment. Their result is a new collection of the same elements. If you add or remove elements from the original collection, this won't affect the copies. Copies may be changed independently of the source as well.

val alice = Person("Alice")
val sourceList = mutableListOf(alice, Person("Bob"))
val copyList = sourceList.toList()
sourceList.add(Person("Charles"))
alice.name = "Alicia"
println("First item's name is: ${sourceList[0].name} in source and ${copyList[0].name} in copy")
println("List size is: ${sourceList.size} in source and ${copyList.size} in copy")
First item's name is: Alicia in source and Alicia in copy
List size is: 3 in source and 2 in copy

Kotlin Official Document

Sample Screenshot