Kotlin 知识

Kotlin中实现对象深拷贝的3种方式

2022-08-29  本文已影响0人  元亨利贞o

〇、Kotlin中的对象拷贝

Kotlin 的 data class 默认提供了一种对象拷贝的方式 , 即 data class 类会生成 copy() 方法, 用于对象的拷贝, 这个方法类似于 java.lang.Objectclone() 方法 ! 值得注意的是: Kotlin 的 data classcopy() 方法 和 java.lang.Objectclone()方法, 都是浅拷贝.

经过测试, 发现 copy() / clone() 方法 返回的对象 的属性都会指被拷贝对象的属性 ( 针对引用类型的属性来说) ! 这就是所谓 \color{red}{浅拷贝}

tips: kotlin用 === 判断引用是否相同, Java用 == 判断引用是否相同


下面是对 clone() 和 copy() 方法的测试:

// Kotlin:   (objCopy 是obj.copy() 返回的对象)
obj === objCopy    // false
obj.fieldA === objCopy.fieldA    // true   (fieldA是除了基本类型和String 之外的其他引用类型)

// Java:  (objCopy 是 obj.clone() 返回的对象)
obj == objCopy  // false
obj.fieldA == objCopy.fieldA // true
Kotlin 的 data class 的一些基本概念:
  1. 数据类会将字段定义为private, 然后提供默认的 getter/setter
  2. 数据类自动重写 equals()、hashCode()、toString() 方法
  3. 会提供一个 copy() 方法, 方便对象的复制, 但是这个 copy() 方法只是浅拷贝 , 对于字段都是 基本数据类型和String 的类来说, 这就足够了。如果类中包含了 基本数据类型和String 之外的引用类型 的字段, 那么必须自己实现深拷贝操作 !!!
  4. 会提供 component#() 方法 (# 为字段编号, 编号从1开始), component# 方法主要用于 解构赋值
    如: val (name,age,sex) = personObject



定义一个 data class

// 数据类会重写: equals, hashCode, toString 这三个方法 并 自动生成 getter/setter
data class Person(var name:String, var age:Int, var sex:Boolean)

编译数据类Person后, 可以使用下列命令来查看字节码文件的内容:
$ javap -p -c com.stone.demo.basic.Person
-p 输出private信息
-c 反编译字节码内容


下面是使用javap反编译一个 data class 类的字节码文件的输出内容, 由于方法的指令部分的内容太多, 已经被删除了, 只留着方法签名部分:

// 注意: 需要先编译出Person.class 文件, 才能使用 javap 反编译:
// javap -p -c com.stone.demo.basic.Person 

// 输出内容如下: 
public final class com.stone.demo.basic.Person {
  // 1. 定义类3个字段: 
  private java.lang.String name;
  private int age;
  private boolean sex;

  // 2. primary 构造函数:
  public com.stone.demo.basic.Person(java.lang.String, int, boolean);

  // 3. 字段对应的 getter/setter
  public final java.lang.String getName();
  public final void setName(java.lang.String);
  public final int getAge();
  public final void setAge(int);
  public final boolean getSex();
  public final void setSex(boolean);

  // 4. 每个字段都生成一个对应的 componen#()  方法    --- # 为字段的编号 , # 从 1 开始
  public final java.lang.String component1();
  public final int component2();
  public final boolean component3();
         
  // 5. copy() 方法:  实例方法copy() 和 静态方法copy$default()
  public final com.stone.demo.basic.Person copy(java.lang.String, int, boolean);
  public static com.stone.demo.basic.Person copy$default(com.stone.demo.basic.Person, java.lang.String, int, boolean, int, java.lang.Object);
        
  // 6. 重写  toString, equals, hashCode 方法
  public java.lang.String toString();
    
  // 重写hashCode方法
  public int hashCode();
      
  // 重写 equals() 方法
  public boolean equals(java.lang.Object);
}



Kotlin中的基本数据类型 (kotlin中这些数据也都是引用类型的, 分别对应于Java的包装类):
Boolean, Byte, Char, Short, Int, Long, Float, Double,
另外 String 会使用常量池 , 深拷贝不考虑它 !

一、Kotlin中深拷贝的实现方式一

DeepCopy.kt 文件 , 内容如下:

package com.stone.demo.basic.deepcopy1

class DeepCopy {
}

interface DeepCopyable<out R> {
    fun deepCopy(): R
}

// 一. kotlin实现对象的深度拷贝
// 实现方式: 使用 直接 "创建对象" 的方式实现深度拷贝
// 优点: 可以用于 "data-class" 和 "非data-class"
// 缺点: a. 需要实现接口并实现接口中的方法, 使用麻烦
//      b. 对于属性层级太深的类型, 实现起来会非常麻烦, 而且有可能导致某些属性无法实现深层拷贝
data class Person(var name:String, var age:Int, var sex: Boolean)
data class Panda (var name:String, var owner:Person) : DeepCopyable<Panda> {
    override fun deepCopy(): Panda {
        return Panda(name, Person(owner.name, owner.age, owner.sex))
    }
}

class A(var name:String)
class B(var field: A) : DeepCopyable<B> {
    override fun deepCopy(): B {
        // 数字, 整型数据类型都有一个缓存 [-128, 127], 拷贝前后的属性对象会指向同一个
        // 字符串常量, 会放置在常量池中, 拷贝前后的属性对象也会指向同一个
        // return B(A(field.name.copy)) // 基本类型 和 String 不需要 拷贝 !!
        return B(A(field.name))
    }
}

fun main() {
    // 0x1: 对于普通类
    val b = B(A("哈哈哈"))
    val bCopy = b.deepCopy()
    println(b === bCopy)                // false
    println(b.field === bCopy.field)      // false

    // 0x2. 对于数据类:
    val panda = Panda("团团", Person("张三", 22, true))
    val pandaCopy = panda.copy()
    val pandaDeepCopy = panda.deepCopy()
    println("===================== for copy")
    println(panda === pandaCopy)        // false
    println(panda === pandaDeepCopy)    // false
    println("===================== for owner-field of copy-object")
    println(panda.owner === pandaCopy.owner)        // true  (浅拷贝, 拷贝后的对象的属性还是会执行源对象的属性)
    println(panda.owner === pandaDeepCopy.owner)    // false
}

二、Kotlin中深拷贝的实现方式二

DeepCopy.kt 文件 , 内容如下:

package com.stone.demo.basic.deepcopy2

import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

class DeepCopy {
}



// 二. 实现深拷贝的方式2  (成员的类型 必须是 数据类 才能实现深度拷贝)
// https://cloud.tencent.com/developer/article/1941174
// 实现: 使用扩展方法 并 通过反射的方式 实现深拷贝
// 优点: 使用简单
// 缺点: 这种方式的深拷贝只对  "data class" 有效,  对于 "非 data class" 则不支 (还是浅拷贝)

fun <T: Any> T.deepCopy():T {
    if(!this::class.isData) { // 不是数据类, 返回对象本身  ( "拷贝" 后的对象指向原对象)
        return this
    }

    // 下面则一长串的逻辑是: 通过反射收集创建 "copy对象" 所需要的参数, 然后通过反射调用primary构造方法创建 "copy对象"

    // 关键点: data class 的primary构造方法的参数是可以通过反射获取到参数名的 且 参数名和类的字段名保持一一对应的关系
    // 通过上面这点, 可以匹配并获得字段 (primary构造方法的参数的运行时值) 的具体值, 然后构造一个参数列表, 最后调用primary构造方法

    // kotlin.reflect.KFunction<out R>  => 构造函数的Kotlin反射类型
    // 拿到primary构造函数 , 然后在primary构造函数上做一些操作, 最后使用此构造函数创建对象 (参数由 this 经过 反射获取)
    return this::class.primaryConstructor!!.let { primaryConstructor ->  // primaryConstructor 是个 java.lang.reflect.Constructor 类型的对象

        // primaryConstructor.parameters 是个 List<KParameter> 类型的对象, 即构造函数的参数列表
        // List的map()函数返回的是一个Iterable对象
        // 这里 primaryConstructor.parameters.map() 的返回结果是一个 Iterable<Pair<*,*>> 类型
        primaryConstructor.parameters.map { parameter ->  // parameter 是一个 KParameter 对象, 即构造函数的某一个参数
            // (this::class as KClass<T>).memberProperties   这玩意是某个类成员属性列表(所有的成员) , 类型为 Collection<KProperty1<T, *>>
            val value = (this::class as KClass<T>).memberProperties.first {
                it.name == parameter.name  // 参数名 和 成员属性 的名称相等时, 取得此成员的值  (data class 的 成员字段的名称 和 primary构造函数的参数的名字是一一对应的)
            }.get(this)

            // 当参数 (也是成员) 的类型是 "数据类" 时, 对参数 (成员) 进行深拷贝 !!
            if((parameter.type.classifier as? KClass<*>)?.isData == true) {
                // 参数 (成员) 为数据类时, 进行深拷贝
                parameter to value?.deepCopy() // 最后一个 (程序的逻辑上的最后一行) 表达式的计算结果作为lambda的返回值时, return 关键字可以省略
            } else {
                // 非数据类, 直接返回字段的值 (不做拷贝)
                parameter to value // 普通类型, 进行浅拷贝
                // key to value => 会构造一个Pair对象, Pair对象是Map的元素

                // to 函数 位于 kotlin包中, 在Tuples.kt文件中定义, 是一个 infix 函数, 具体定义如下:
                // public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
                // infix 函数, 为中缀函数, 可以省略 函数调用符  "."  和  表示函数的 "()"
            }
        }.toMap().let(primaryConstructor::callBy) // 通过反射收集创建 "copy对象" 所需要的参数, 然后通过反射调用primary构造方法创建 "copy对象"
        // Iterable<Pair<*,*>> 类型可以转换为Map对象
    }
}



// a. 数据类
data class A(var name:String)
data class B(var field:A)

// b. 非数据类
class C(var name:String)
class D(var field:C)

fun main() {
    // 1. 数据类
    val b = B(A("哈哈"))
    val bCopy = b.deepCopy()
    println(b === bCopy)                // false
    println(b.field === bCopy.field)      // false

    // 2. 非数据类
    val d = D(C("哈哈"))
    val dCopy = d.deepCopy()
    println(d === dCopy)              // true
    println(d.field === dCopy.field)    // true
}

三、Kotlin中深拷贝的实现方式三

DeepCopy.kt 文件 , 内容如下:

package com.stone.demo.basic.deepcopy3

import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

class DeepCopy {
}

// 三. DeepCopyable接口 和 数据类 结合的方式 (优先使用DeepCopyable接口的deepCopy()方法进行深拷贝)
// 实现: 反射调用DeepCopyable接口的deepCopy()方法 或者 反射调用 "data class类" 的copy() 方法  进行深拷贝!
// 优点: 可实现深拷贝的对象范围更大 (实现了DeepCopyable接口的对象, "data class类" 的对象)
// 缺点: 对于既没有实现 DeepCopyable 接口 也不是 "data class类" 的对象, 仍然无法进行深拷贝 !!

// 注意:
// 扩展方法名如果能与DeepCopyable接口的方法 同名, 则对象在调用deepCopy时, 会优先调用对象本身定义的方法,
// 而不是调用扩展方法 !!
// 如果要统一调用 扩展方法deepCopy() , 那么需要将 DeepCopyable 接口的方法更改为其他名称,
// 下面的扩展方法需要更改的地方有两点:
// 1. 下面的扩展方法中的步骤1中 查找 DeepCopyable 接口的方法的条件 应更改为: it.name = "DeepCopyable中深拷贝方法名"
// 2. 下面的扩展方法中的步骤2中 参数收集的地方, 如果参数实现了DeepCopyable接口, 则参数的深拷贝应改为: value?.DeepCopyable接口中深拷贝方法() , 如 value?.dc()
fun <T : Any> T.deepCopy(): Any? { // 注意: deepCopy的返回值虽然声明为Any? , 但是, 其真实类型仍然是 T
                                    // 调用deepCopy() 后 可以对其进行转型
                                    // 如: val a:Type? = typeObject.deepCopy() as? Type
    println("扩展方法 deepCopy() 被调用 !")

    // 1. 对于实现了 DeepCopyable<R> 接口的类, 直接通过反射调用 deepCopy() 方法进行深拷贝
    if(this is DeepCopyable<*>) { // 如果扩展方法 和 DeepCopyable中的方法 同名, 则对象调用深拷贝方法时, 不会调用扩展方法  (这样一来, 这段判断就没有用了 !).
                                    // 因为类本身的深拷贝方法会被优先调用.
        // kotlin方式调用有问题: Exception in thread "main" java.lang.IllegalArgumentException: Callable expects 1 arguments, but 0 were provided.
        /*
        return this::class.declaredMemberFunctions.first {
            it.name == "deepCopy"
        }.call()*/

        // 使用Java的反射调用:
        return this::class.java.declaredMethods.first {
            it.name == "deepCopy" // deepCopy 是 DeepCopyable<R> 接口中实现深拷贝的方法
                                    // 如果要同一调用裤子方法来进行深拷贝, 可以将DeepCopyable中的深拷贝方法, 进行更名,
                                    // 如 dc,  那么此处的条件也需要更改为: it.name == "dc"
        }.invoke(this)
    }


    // https://cloud.tencent.com/developer/article/1941174
    // 2. 如果是数据类 (data class) , 则通过反射调用数据类的copy()方法进行深拷贝
    //    注意: 需要考虑对象的字段的类型, 字段的类型分为3种:
    //          1. 实现了 DeepCopyable<R> 接口的字段
    //          2. data class
    //          3. 非上述两类 (这一大类分为两种情况: a. 基本数据类型和String --- 这种属性拷贝是没有问题的
    //                                          b. 普通的引用类型 (既没有实现DeepCopyable接口, 也不是data class)  --- 这种属性只能实现浅拷贝了 )
    if(this::class.isData) {
        // copy()方法的的参数列表 和 primary构造方法的 参数列表是一样的,
        // 但是copy()方法的参数列表的参数是无法获取到参数名称的, primary构造方法的参数是可以获取到参数名的, 且参数名就是类的字段名称

        /*
        // data class 的 copy() 方法  (这种方式找到的copy方法, 是kotlin版本的copy, 而调用时调用的字节码中的copy方法, 字节码中的copy方法是java版本的copy方法)
        val dataClassCopyMethod = this::class.declaredMemberFunctions.first { copyMethod ->
            copyMethod.name == "copy"
        }
        // 字节码中的方法签名是: copy(java.lang.String)
        // 获取到的方法前面却是: copy(kotlin.String)
        println("dataClassCopyMethod => $dataClassCopyMethod") // fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
        // dataClassCopyMethod 的 类型是 KFunction<*>,  如何将 KFunction<*> 转换成 Java的java.lang.reflect.Method ??
        // 答案是, 直接找java版本的copy方法, 而不是找kotlin版本的copy方法
         */

        // 查找copy()方法, 使用Java版本的copy()方法
        val dataClassCopyMethod = this::class.java.declaredMethods.first { copyMethod ->
            copyMethod.name == "copy"  // copy 是 数据类中提供的copy()方法
        }
        // println("dataClassCopyMethod => $dataClassCopyMethod") // copy(kotlin.String): com.stone.demo.basic.A

        // data class 的 primary构造方法
        val primaryConstructor = this::class.primaryConstructor

        /*
        // 使用primary构造方法的参数类收集copy()方法的运行时参数, 而不是使用copy()方法的参数来收集copy方法的运行时参数.
        // 原因是: copy() 方法的参数名是获取不到的, primary构造方法的参数数名称是可以获取到的, 且参数名就是字段的名称
        return dataClassCopyMethod.parameters.map { parameter ->
            // java.util.NoSuchElementException: Collection contains no element matching the predicate.
            val value = (this::class as KClass<T>).memberProperties.first {
                println("it.name=${it.name} , parameter.name=${parameter.name} , parameter.type=${parameter.type} , it.javaClass.typeName=${it.javaClass.typeName}")
                // parameter.name  的值 是 null   => copy() 方法的参数名是获取不到的, primary构造方法的参数数名称是可以获取到的, 且参数名就是字段的名称
                it.name == parameter.name
            }.get(this)
            parameter to value?.deepCopy1()
        }.toMap().let( dataClassCopyMethod::callBy )
         */

        /*
        return primaryConstructor?.parameters?.map { parameter ->
            // java.util.NoSuchElementException: Collection contains no element matching the predicate.
            val value = (this::class as KClass<T>).memberProperties.first {
                println("it.name=${it.name} , parameter.name=${parameter.name} , parameter.type=${parameter.type} , it.javaClass.typeName=${it.javaClass.typeName}")
                it.name == parameter.name
            }.get(this)

            if(value is DeepCopyable1<*>) {
                parameter to value?.deepCopy()
            } else if(value != null && value::class.isData) { // 如果是数据类, 就递归调用 deepCopy1()
                println("value => $value")
                // java.lang.IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
                // data class A(val name: String) 类的copy()方法, 编译后的签名是: copy(java.lang.String)  , 而不是 copy(kotlin.String)
                parameter to value.deepCopy1()
            } else {
                // 此处再做一下处理, 否则出现: // java.lang.IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
                if(value is String) {
                    parameter to java.lang.String(value)
                } else {
                    parameter to value
                }

                // Exception in thread "main" java.lang.IllegalArgumentException:
                // No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
            }
        }?.toMap()?.let( dataClassCopyMethod::callBy )  // kotlin 反射方法的调用方式
         */

        val params = primaryConstructor?.parameters?.map { parameter ->
            val value = (this::class as KClass<T>).memberProperties.first {
                // println("it.name=${it.name} , parameter.name=${parameter.name} , parameter.type=${parameter.type} , it.javaClass.typeName=${it.javaClass.typeName}")
                it.name == parameter.name
            }.get(this)

            // 数据类的字段分为三种:
            // 1. 实现了 DeepCopyable<R> 接口的字段
            // 2. data class
            // 3. 非上述两类 (这一大类分为两种情况:
            //      a. 基本数据类型和String --- 这种属性拷贝是没有问题的
            //      b. 普通的引用类型 (既没有实现DeepCopyable接口, 也不是data class)  --- 这种属性只能实现浅拷贝了
            //
            // 对于三种类型的字段分别实现相对于的参数收集逻辑 !!
            if(value is DeepCopyable<*>) {
                value?.deepCopy()   // 如果 要同一使用 扩展方法来进行深拷贝, 则 DeepCopyable 方法需要更名为其他名字,
                                    // 且此处改为:  value?.DeepCopyable接口中进行深拷贝的方法()
                                    // 例如: DeepCopyable 接口中深拷贝方法为 dc(), 则此处 应为: value?.dc()
            } else if(value != null && value::class.isData) { // 如果是数据类, 就递归调用 deepCopy()
                // println("value => $value")
                // java.lang.IllegalArgumentException: No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
                // data class A(val name: String) 类的copy()方法, 编译后的签名是: copy(java.lang.String)  , 而不是 copy(kotlin.String)
                value.deepCopy() // 这里value.deepCopy() 是递归调用当前 (扩展) 方法
            } else {
                value   // 返回属性本身 (浅拷贝)
                // Exception in thread "main" java.lang.IllegalArgumentException:
                // No argument provided for a required parameter: instance parameter of fun com.stone.demo.basic.A.copy(kotlin.String): com.stone.demo.basic.A
            }
        }?.toTypedArray()

        // 注意: return 不能少, 否则会 调用最后的 "return this"
        return if(params == null) {
            dataClassCopyMethod.invoke(this)
        } else {
            // https://blog.csdn.net/xlh1191860939/article/details/82109086
            // 将 Array<T> 转换为可变参数   *arrayVar
            dataClassCopyMethod.invoke(this, *params)
        }
    }

    // 3. 既没有实现 DeepCopyable<R> 接口, 也不是  "data class 类,  直接返回对象本身 (浅拷贝 --- 引用拷贝 --- 只是在栈上拷贝了一个引用,这个引用仍然指向源对象 !)
    return this
}


// a. 实现了 DeepCopyable 接口的类
interface DeepCopyable<out R> {
    fun deepCopy(): R
}

data class Person(var name:String, var age:Int, var sex: Boolean)
data class Panda (var name:String, var owner:Person) : DeepCopyable<Panda> {
    override fun deepCopy(): Panda {
        return Panda(name, Person(owner.name, owner.age, owner.sex))
    }
}

// b. 数据类
data class A(var name:String)
data class B(var field:A)

// c. 非数据类
class C(var name:String)
class D(var field:C)

// 测试代码:
fun main() {
    // 1. 实现了 DeepCopyable 接口的类的对象
    val panda = Panda("圆圆", Person("张三", 32, true))
    val pandaDeepCopy = panda.deepCopy()    // 此处实际上是调用 DeepCopyable 的 deepCopy()方法, 而不是调用扩展方法 deepCopy()
                                            // 由输出信息可以看出 !
                                            // 扩展函数中的 println("扩展方法 deepCopy() 被调用 !") 此句代码并未被调用 !
    println("======================= test for DeepCopyable")
    println(panda === pandaDeepCopy)             // false
    println(panda.owner === pandaDeepCopy.owner) // false

    // 2. data class 类 的对象
    println("======================= test for data class")
    val b = B(A("哈哈"))
    val bCopy = b.deepCopy() as B
    println(b === bCopy)           // false
    println(b.field === bCopy.field) // false

    // 3. 非上述两类对象
    println("======================= test for other")
    val d = D(C("嘻嘻"))
    val dCopy = d.deepCopy() as D
    println(d === dCopy)            // true
    println(d.field === dCopy.field)  // true
}

References:Kotlin | 实现数据类(data)深拷贝

上一篇下一篇

猜你喜欢

热点阅读