João Freitas

The following is quick explanation on the differences between shallow and deep copy, and both can be used in Kotlin.

https://blog.protein.tech/kotlin-shallow-vs-deep-copy-explained-907a72ccbf7a


In this article, I’ll explain the difference between deep and shallow copying in Kotlin and why it’s crucial to understand it when copying data classes and lists.

Shallow Copy

A shallow copy creates a new object that is a copy of the original object, but it does not create new copies of the nested objects. Instead, the new object simply contains references to the same nested objects as the original object.

Confused? Let me explain it with an example :)

To shallow copy a data class in Kotlin, we can use copy() function

data class Address(var city: String)  

data class User(val name: String, val surname: String, val address: Address)  

val address = Address("New York")  
val originalUser = User("John", "Smith", address)  
val copiedUser = originalUser.copy(name = "Ilyas")  

println(originalUser) // User(name=John, surname=Smith, address=Address(city=New York))  
println(copiedUser) // User(name=Ilyas, surname=Smith, address=Address(city=New York))  

// Change the city in original user's address  
originalUser.address.city = "San Francisco"  

println(originalUser) // User(name=John, surname=Smith, address=Address(city=San Francisco))  
println(copiedUser) // User(name=Ilyas, surname=Smith, address=Address(city=San Francisco))

Note that after copying the user, only the name was altered, and everything else remained the same. However, when we modified the city in the originalUser, the copiedUser was also affected

This happens because copy function didn’t copy the nested objects values (address) instead it just assigned the reference of the address to copiedUser.

If you took a look at the copy implementation it would make total sense…

fun copy(  
    name: String = this.name,  
    surname: String = this.surname,  
    address: Address = this.address  
): User = User(name, surname, address)

Note: We wouldn’t face this problem if all of the data class’s properties are immutables. That’s one of the reasons to always have immutable properties.

Another thing to notice is copy function only copies the properties in the primary constructor. That means that it won’t copy any property you define inside the class body.

data class User(val name: String, ...){  
    var isChanged = false  
}  

val originalUser = User("John", ...)  
originalUser.isChanged = true  

val copiedUser = originalUser.copy(name = "Ilyas")  

println(originalUser.isChanged) // true  
println(copiedUser.isChanged) // surprise surprise, it's false 🙂

This is normally a preferred behavior but you need to be careful about it.

Deep copy

A deep copy creates a new object that is a copy of the original object and (recursively) all its nested objects. Therefore, any changes made to the nested objects in the original object will not affect the new object.

Kinda abstract right? Let me show you examples…

To achieve deep copy in Kotlin you can implement Cloneable interface and provide a custom implementation for it.

data class Address(var city: String): Cloneable {...}  
    
data class User(val name: String, val address: Address): Cloneable{  
    var isChanged = false  

    public override fun clone(): User {  
        return User(this.name, this.address.clone()).also {  
            it.isChanged = this.isChanged  
        }  
    }  
}

This implementation can be hard and requires a lot of boilerplate code, (imagine having 10 nested properties). Instead, we can use Json to convert the class to String and then use it to create a new deep-copied object.

data class User(val name: String, val address: Address): Cloneable {  
    var isChanged = false  

    public override fun clone(): User {  
        return gson.toJson(this).let { gson.fromJson(it, User::class.java) }  
    }  
}  

val address = Address("New York")  
val originalUser = User("John", address)  
originalUser.isChanged = true  

val clonedUser = originalUser.clone()  
originalUser.address.city = "San Francisco"  

println("Original: $originalUser, isChanged: ${originalUser.isChanged}")  
// Original: User(name=John, address=Address(city=San Francisco)), isChanged: true  
println("Cloned: $clonedUser, isChanged: ${clonedUser.isChanged}")  
// Cloned: User(name=John, address=Address(city=New York)), isChanged: true

Note that we didn’t encounter any of the issues that we faced in the shallow copy (changing the city of the originalUser didn’t affect the clonedUser + isChanged copied successfully) 🤩💃

Finally here is how you copy a list of objects.

Conclusion

Although deep copying creates a complete replica of an object, it can be slower and requires more code compared to shallow copying. Therefore, I recommend using data classes with immutable properties over deep copying. And I hope you loved the blog 💜

#reads #ilyas ipek #kotlin #shallow copy #deep copy