添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
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())

以上代码,运行结果是什么?可选项:

  • one, two
  • one, two, oops
  • UnsupportedOperationException
  • will not compile
  • 思考一下,记录下你心中的答案。

    Kotlin 编译器帮 data class 生成的 copy 函数是深拷贝还是浅拷贝?

  • 若是深拷贝,答案就是 one, two
  • 若是浅拷贝,答案就是 one, two, oops
  • 深拷贝、浅拷贝

    深拷贝和浅拷贝只针对ObjectArray这样的引用数据类型。

  • 浅拷贝(Shadow Clone) 浅拷贝只复制对象应用,即指向对象的指针,而不复制对象本身,新旧对象共享同一块内存。
  • 但是,componentNcopy()函数不允许被覆写。更重要的一点,copy() 函数完成的是浅拷贝

    因此题目中,c1c2 对象中成员变量list指向同一份内存空间(即变量list),所以当list改变时,由于两个对象中的成员变量均会改变。

    所以,正确答案为 :

    选项 2 :one, two, oops

    虽然 data class 的设计初衷是帮助开发者持有一些不可变的数据,以便区分数据类和业务类,但是 Kotlin 在编译上并未做严格的限制,再加上copy函数浅拷贝的问题,程序上很容易出现与题目中相似的问题。开发者在抱怨设计缺陷的同时,也在积极寻找解决办法。

    noCopy

    一个从数据类中删除了 copy 方法的编译器插件。源码地址:github.com/AhmedMourad…

    buildscript {
        repositories {
            mavenCentral()
            // Or
            maven { url "https://plugins.gradle.org/m2/" }
        dependencies {
            classpath "dev.ahmedmourad.nocopy:nocopy-gradle-plugin:1.4.0"
    
    plugins {
      id "dev.ahmedmourad.nocopy.nocopy-gradle-plugin" version "1.4.0"
    

    通过 @NoCopy 注解实现功能

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

    deepCopy

    BennyHuo 老师为你解忧,提供两种深拷贝的方法:一种基于 Kotlin 反射,一种基于 KAPT。源码地址:github.com/bennyhuo/Ko…

    implementation("com.bennyhuo.kotlin.reflect:deepcopy-reflect:1.5.0")
    
    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)
    
    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")
    
  • 添加 @DeepCopy 注解
  • @DeepCopy
    data class Speaker(val name: String, val age: Int)
    @DeepCopy
    data class Talk(val name: String, val speaker: Speaker)
    
    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()) 
    

    注意:如果成员属性是使用 @DeepCopy 注释的数据类,程序将会递归调用 deepCopy

    Kotlin Android Android Jetpack
    私信
     3,625