Reputation: 51059
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?
Upvotes: 120
Views: 157553
Reputation: 11
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
Upvotes: 1
Reputation: 47137
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")
Upvotes: 40
Reputation: 1683
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).
Upvotes: 2
Reputation: 1059
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()
Upvotes: 42
Reputation: 309
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)
}
}
Upvotes: 1
Reputation: 11
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.
Upvotes: 1
Reputation: 1771
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
}
Upvotes: 4
Reputation: 1861
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
}
Upvotes: 10
Reputation: 423
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
Upvotes: 7
Reputation: 97178
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.
Upvotes: 123