〇樊拓、Kotlin中的對象拷貝
Kotlin 的 data class
默認提供了一種對象拷貝的方式 , 即 data class
類會生成 copy()
方法, 用于對象的拷貝, 這個方法類似于 java.lang.Object
的 clone()
方法 ! 值得注意的是: Kotlin 的 data class
的 copy()
方法 和 java.lang.Object
的clone()
方法, 都是淺拷貝.
經過測試, 發(fā)現(xiàn) copy() / clone()
方法 返回的對象 的屬性都會指被拷貝對象的屬性 ( 針對引用類型的屬性來說) ! 這就是所謂
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
的一些基本概念:
- 數據類會將字段定義為private, 然后提供默認的 getter/setter
- 數據類自動重寫 equals()、hashCode()冤狡、toString() 方法
- 會提供一個 copy() 方法, 方便對象的復制, 但是這個 copy() 方法只是淺拷貝 , 對于字段都是 基本數據類型和String 的類來說, 這就足夠了驰吓。如果類中包含了 基本數據類型和String 之外的引用類型 的字段, 那么必須自己實現(xiàn)深拷貝操作 !!!
- 會提供 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后, 可以使用下列命令來查看字節(jié)碼文件的內容:
$ javap -p -c com.stone.demo.basic.Person
-p 輸出private信息
-c 反編譯字節(jié)碼內容
下面是使用javap反編譯一個 data class
類的字節(jié)碼文件的輸出內容, 由于方法的指令部分的內容太多, 已經被刪除了, 只留著方法簽名部分:
// 注意: 需要先編譯出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() 和 靜態(tài)方法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中深拷貝的實現(xiàn)方式一
DeepCopy.kt 文件 , 內容如下:
package com.stone.demo.basic.deepcopy1
class DeepCopy {
}
interface DeepCopyable<out R> {
fun deepCopy(): R
}
// 一. kotlin實現(xiàn)對象的深度拷貝
// 實現(xiàn)方式: 使用 直接 "創(chuàng)建對象" 的方式實現(xiàn)深度拷貝
// 優(yōu)點: 可以用于 "data-class" 和 "非data-class"
// 缺點: a. 需要實現(xiàn)接口并實現(xiàn)接口中的方法, 使用麻煩
// b. 對于屬性層級太深的類型, 實現(xiàn)起來會非常麻煩, 而且有可能導致某些屬性無法實現(xiàn)深層拷貝
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 (淺拷貝, 拷貝后的對象的屬性還是會執(zhí)行源對象的屬性)
println(panda.owner === pandaDeepCopy.owner) // false
}
二、Kotlin中深拷貝的實現(xiàn)方式二
DeepCopy.kt 文件 , 內容如下:
package com.stone.demo.basic.deepcopy2
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
class DeepCopy {
}
// 二. 實現(xiàn)深拷貝的方式2 (成員的類型 必須是 數據類 才能實現(xiàn)深度拷貝)
// https://cloud.tencent.com/developer/article/1941174
// 實現(xiàn): 使用擴展方法 并 通過反射的方式 實現(xiàn)深拷貝
// 優(yōu)點: 使用簡單
// 缺點: 這種方式的深拷貝只對 "data class" 有效, 對于 "非 data class" 則不支 (還是淺拷貝)
fun <T: Any> T.deepCopy():T {
if(!this::class.isData) { // 不是數據類, 返回對象本身 ( "拷貝" 后的對象指向原對象)
return this
}
// 下面則一長串的邏輯是: 通過反射收集創(chuàng)建 "copy對象" 所需要的參數, 然后通過反射調用primary構造方法創(chuàng)建 "copy對象"
// 關鍵點: data class 的primary構造方法的參數是可以通過反射獲取到參數名的 且 參數名和類的字段名保持一一對應的關系
// 通過上面這點, 可以匹配并獲得字段 (primary構造方法的參數的運行時值) 的具體值, 然后構造一個參數列表, 最后調用primary構造方法
// kotlin.reflect.KFunction<out R> => 構造函數的Kotlin反射類型
// 拿到primary構造函數 , 然后在primary構造函數上做一些操作, 最后使用此構造函數創(chuàng)建對象 (參數由 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) // 通過反射收集創(chuàng)建 "copy對象" 所需要的參數, 然后通過反射調用primary構造方法創(chuàng)建 "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中深拷貝的實現(xiàn)方式三
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接口 和 數據類 結合的方式 (優(yōu)先使用DeepCopyable接口的deepCopy()方法進行深拷貝)
// 實現(xiàn): 反射調用DeepCopyable接口的deepCopy()方法 或者 反射調用 "data class類" 的copy() 方法 進行深拷貝!
// 優(yōu)點: 可實現(xiàn)深拷貝的對象范圍更大 (實現(xiàn)了DeepCopyable接口的對象, "data class類" 的對象)
// 缺點: 對于既沒有實現(xiàn) DeepCopyable 接口 也不是 "data class類" 的對象, 仍然無法進行深拷貝 !!
// 注意:
// 擴展方法名如果能與DeepCopyable接口的方法 同名, 則對象在調用deepCopy時, 會優(yōu)先調用對象本身定義的方法,
// 而不是調用擴展方法 !!
// 如果要統(tǒng)一調用 擴展方法deepCopy() , 那么需要將 DeepCopyable 接口的方法更改為其他名稱,
// 下面的擴展方法需要更改的地方有兩點:
// 1. 下面的擴展方法中的步驟1中 查找 DeepCopyable 接口的方法的條件 應更改為: it.name = "DeepCopyable中深拷貝方法名"
// 2. 下面的擴展方法中的步驟2中 參數收集的地方, 如果參數實現(xiàn)了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. 對于實現(xiàn)了 DeepCopyable<R> 接口的類, 直接通過反射調用 deepCopy() 方法進行深拷貝
if(this is DeepCopyable<*>) { // 如果擴展方法 和 DeepCopyable中的方法 同名, 則對象調用深拷貝方法時, 不會調用擴展方法 (這樣一來, 這段判斷就沒有用了 !).
// 因為類本身的深拷貝方法會被優(yōu)先調用.
// 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> 接口中實現(xiàn)深拷貝的方法
// 如果要同一調用褲子方法來進行深拷貝, 可以將DeepCopyable中的深拷貝方法, 進行更名,
// 如 dc, 那么此處的條件也需要更改為: it.name == "dc"
}.invoke(this)
}
// https://cloud.tencent.com/developer/article/1941174
// 2. 如果是數據類 (data class) , 則通過反射調用數據類的copy()方法進行深拷貝
// 注意: 需要考慮對象的字段的類型, 字段的類型分為3種:
// 1. 實現(xiàn)了 DeepCopyable<R> 接口的字段
// 2. data class
// 3. 非上述兩類 (這一大類分為兩種情況: a. 基本數據類型和String --- 這種屬性拷貝是沒有問題的
// b. 普通的引用類型 (既沒有實現(xiàn)DeepCopyable接口, 也不是data class) --- 這種屬性只能實現(xiàn)淺拷貝了 )
if(this::class.isData) {
// copy()方法的的參數列表 和 primary構造方法的 參數列表是一樣的,
// 但是copy()方法的參數列表的參數是無法獲取到參數名稱的, primary構造方法的參數是可以獲取到參數名的, 且參數名就是類的字段名稱
/*
// data class 的 copy() 方法 (這種方式找到的copy方法, 是kotlin版本的copy, 而調用時調用的字節(jié)碼中的copy方法, 字節(jié)碼中的copy方法是java版本的copy方法)
val dataClassCopyMethod = this::class.declaredMemberFunctions.first { copyMethod ->
copyMethod.name == "copy"
}
// 字節(jié)碼中的方法簽名是: 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 {
// 此處再做一下處理, 否則出現(xiàn): // 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. 實現(xiàn)了 DeepCopyable<R> 接口的字段
// 2. data class
// 3. 非上述兩類 (這一大類分為兩種情況:
// a. 基本數據類型和String --- 這種屬性拷貝是沒有問題的
// b. 普通的引用類型 (既沒有實現(xiàn)DeepCopyable接口, 也不是data class) --- 這種屬性只能實現(xiàn)淺拷貝了
//
// 對于三種類型的字段分別實現(xiàn)相對于的參數收集邏輯 !!
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. 既沒有實現(xiàn) DeepCopyable<R> 接口, 也不是 "data class 類, 直接返回對象本身 (淺拷貝 --- 引用拷貝 --- 只是在棧上拷貝了一個引用,這個引用仍然指向源對象 !)
return this
}
// a. 實現(xiàn)了 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. 實現(xiàn)了 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 | 實現(xiàn)數據類(data)深拷貝