“Interesting Kotlin” series, through solving problems to deepen their understanding of Kotlin.

0 x0b: Copy

data class Container(val list: MutableList<String>)
​
fun main(args: Array<String>) {
    val list = mutableListOf("one", "two")
    val c1 = Container(list)
    val c2 = c1.copy()
    list += "oops"
    println(c2.list.joinToString())
}
Copy the code

What is the result of running the above code? Optional:

  1. one, two
  2. one, two, oops
  3. UnsupportedOperationException
  4. will not compile

Think about it and write down the answers in your mind.

Analysis of the

Is the copy function generated by the Kotlin compiler for the data class a deep copy or a shallow copy?

  • In the case of deep copy, the answer is one, two
  • For shallow copy, the answer would be one, two, oops

Deep copy, shallow copy

Deep and shallow copies are only for reference data types such as Object and Array.

  • Shadow Clone A shallow copy copies only the application of the object, that is, the pointer to the object, but not the object itself. The old and new objects share the same memory.

  • Deep Clone A Deep Clone creates an identical object. The new object does not share memory with the original object. The new object does not change to the original object.

Pictures from: stackoverflow.com/questions/1…

Data Class

In Kotlin, the compiler helps the data class generate the following member functions:

  • equals()hashCode()
  • toString()
  • componentN
  • copy()

However, the componentN and copy() functions are not allowed to be overridden. More importantly, the copy() function does a shallow copy.

So in the question, the list of member variables in the c1 and C2 objects point to the same memory space (the list of variables), so when the list changes, because the member variables in both objects change.

The correct answer is:

Option 2: One, two, oops

extension

Although the data class is designed to help developers hold immutable data so that they can distinguish between data classes and business classes, Kotlin’s lack of strict compilation restrictions, coupled with the shallow copy problem of the copy function, can easily cause problems similar to the one in the title. While developers complain about design flaws, they are also actively looking for solutions.

noCopy

A compiler plug-in that removes the copy method from the data class. Source address: github.com/AhmedMourad… .

Add the dependent
buildscript { repositories { mavenCentral() // Or maven { url "https://plugins.gradle.org/m2/" } } dependencies { Classpath "dev. Ahmedmourad. Nocopy: nocopy - gradle - plugin: 1.4.0"}}Copy the code
Plugins {id "dev. Ahmedmourad. Nocopy. Nocopy - gradle - plugin" version "1.4.0}"Copy the code
use

Functionality is realized through @noCopy annotation

@NoCopy data class User(val name: String, val phoneNumber: String) User("Ahmed", "+201234567890").copy(phoneNumber = "Happy birthday!" ) // Unresolved reference: copyCopy the code

deepCopy

BennyHuo provides you with two deep copy methods: one based on Kotlin reflection and one based on KAPT. Source address: github.com/bennyhuo/Ko…

reflection
  • Add the dependent
Implementation (" com. Bennyhuo. Kotlin. Reflect: deepcopy - reflect: 1.5.0 ")Copy the code
  • use
data class Speaker(val name: String, val age: Int) data class Talk(val name: String, val speaker: Speaker) class DeepCopyTest { @Test fun test() { val talk = Talk("DataClass in Action", Speaker("Benny Huo", 30)) val newTalk = talk.deepCopy() assert(talk == newTalk) assert(talk ! == newTalk) } }Copy the code
KAPT
  • Add the dependent
apply plugin: "kotlin-kapt" ... Dependencies {kapt (" com. Bennyhuo. Kotlin. Apt: deepcopy - compiler: 1.5.0 ") Implementation (" com. Bennyhuo. Kotlin. Apt: deepcopy - runtime: 1.5.0 ")}Copy the code
  • add@DeepCopyannotations
@DeepCopy
data class Speaker(val name: String, val age: Int)
@DeepCopy
data class Talk(val name: String, val speaker: Speaker)
Copy the code
  • use
fun Talk.deepCopy(name: String = this.name, speaker: Speaker = this.speaker): Talk = Talk(name, speaker.deepCopy())

fun Speaker.deepCopy(
    name: String = this.name,
    age: Int = this.age,
    company: Company = this.company
): Speaker = Speaker(name, age, company.deepCopy()) 
Copy the code

Note: If the member attribute is a data class annotated with @deepCopy, DeepCopy will be called recursively.