前言
最近在學(xué)習(xí)kotlin的反射的時(shí)候遇到了一些問題,特地記錄一下。
正題
在Java中使用Class很常見的就是,xxx類.class,比如我們?cè)趕tartActivity的時(shí)候startActivity(new Intent(this, OtherActivity.class)); 這里接收的就是CLass<?> cls參數(shù)。
那么在java中獲取Class的方法有哪些呢?
1、Class c = person.getClass(); //對(duì)象獲取
2、Class cc =Person.class;//類獲取
而我們來看看kotlin
//對(duì)象獲取
person.javaClass// javaClass
person::class.java // javaClass
//類獲取
Person::class// kClass
person.javaClass.kotlin// kClass
(Person::class as Any).javaClass// javaClass
Person::class.java // javaClass
哇,這么多種,他們是不是一樣的,有沒有什么區(qū)別?
log看看他們到底是不是相同的Class
println(person.javaClass == person::class.java) //true
println(person.javaClass == Person::class.java)//true
println(person::class.java == Person::class.java)//true
//person.javaClass == person::class.java == Person::class.java
println(person.javaClass == Person::class)//false
println(person.javaClass.kotlin == Person::class)//true
println(person::class == Person::class)//true
從log來看,person.javaClass == person::class.java == Person::class.java
三者是相同的。但是person.javaClass == Person::class卻是不同的。為什么呢?
原因是在kotlin中的Class與Java不同,kotlin中有一個(gè)自己的Class叫做KClass,person::class 和Person::class都是獲取kotlin的KClass,所以println(person::class == Person::class) 為true。
我們可以從kotlin的KClass獲取到j(luò)ava的Class,person::class.java就是如此,先獲取到kotlin的KClass然后再獲取javaClass。
object/class->kClass->Class
同樣也可以通過java的Class獲取kotlin的KClass,person.javaClass.kotlin就是先獲取javaClass然后再獲取kotlin的KClass
object/class->Class->KClass
那么KClass都有些什么用呢?Find Usages 可以看到

幾乎多數(shù)跟Reflect相關(guān),而用的最多的也是在KClasses里面
KClasses擴(kuò)展了許多跟反射相關(guān)的方法,算的上是kotlin的反射中類主力輸出。

如果要使用kotlin的反射類的話,要加入
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

舉個(gè)例子
我們要把一個(gè)類的所有字段通過反射給打印出來,調(diào)用java的方法來實(shí)現(xiàn)是這樣
println (Person::class.java.declaredFields.map {
it.isAccessible = true
"${it.name}: ${it.get(person)}"
}.joinToString(","))
我們通過Person::class拿到KClass,然后.java拿到j(luò)ava的Class<?>,再獲取declaredFields,最后通過map,然后把獲取的Field獲取到值打印出來
問題
而使用kotlin,我們還有別的做法
println (Person::class.memberProperties.map {
it.isAccessible = true
"${it.name}: ${it.get(person)}"
}.joinToString(","))
通過Person::class拿到KClass,直接調(diào)用KClass的memberProperties來拿到KProperty的Collection集合,然后進(jìn)行操作。當(dāng)然,這里KProperty也是kotlin的反射類中的,也類似于Java的Field。
那么既然可以這樣,理論上這樣也應(yīng)該是可以的
//person是對(duì)象不是Person類
println (person::class.memberProperties.map {
it.isAccessible = true
"${it.name}: ${it.get(person)}"
}.joinToString(","))
這下卻出錯(cuò)了,真的奇怪!
Error:(73, 31) Out-projected type 'KProperty1<out OtherActivity.Person, Any?>' prohibits the use of 'public abstract fun get(receiver: T): R defined in kotlin.reflect.KProperty1'
為什么會(huì)這樣?
查看it發(fā)現(xiàn)

kotlin的property的Person為out逆變的,R只能作為輸出,不能作為get的參數(shù)傳入,所報(bào)錯(cuò)了。
這里的kotlin泛型還是有點(diǎn)小坑的需要你踩一踩,網(wǎng)上有一篇文章解釋說明了一番
那么怎么改呢?
三種辦法:
//擴(kuò)展KProperty的get方法為getUnsafed,其實(shí)就是去掉了out
fun <T, R> KProperty1<T, R>.getUnsafed(receiver: Any): R {
return get(receiver as T)
}
//然后
println(person::class.memberProperties.map {
it.isAccessible = true
"${it.name}: ${it.getUnsafed(person)}"
}.joinToString(","))
//強(qiáng)轉(zhuǎn)一下
println(person::class.memberProperties.map {
it.isAccessible = true
it as KProperty1<Person, Any>
"${it.name}: ${it.get(person)}"
}.joinToString(","))
//這一種就涉及到kotlin中的獲取KClass的方式, 先獲取java的Class再獲取kotlin的KClass
//神奇的是,這種獲取到的it的類型沒有out,而是我們期望的 KProperty1<Person, Any>
println(person.javaClass.kotlin.memberProperties.map {
it.isAccessible = true
"${it.name}: ${it.get(person)}"
}.joinToString(","))
最后
在kotlin學(xué)習(xí)和探索的過程中發(fā)現(xiàn)了這些問題,非常地好奇,想要探知其原理。所以摸索著前進(jìn),希望能帶給其他人一些學(xué)習(xí)的思路和興趣。
另外,還有一個(gè)問題就是:
為什么有時(shí)候map的it是帶out的有時(shí)候不帶?這個(gè)跟class是java的class還是kotlin的class是否有關(guān)系?其間的原理和過程是怎么樣的,我還在繼續(xù)探索,希望明白的同學(xué)可以分享一下。