Kotlin知識歸納(十四) —— 反射

大棋發表於2019-07-23

前序

      當在Kotlin中使用反射時,你會和兩種不同的反射API打交道。

  • 標準的Java反射,定義在包 java.lang.reflect 中。因為Kotlin類會被編譯成普通的Java位元組碼,Java反射API可以完美地支援它們。
  • Kotlin反射API,定義在包kotlin.reflect中。通過Kotlin反射API你可以訪問那些在Java世界裡不存在的概念,諸如屬性和可空型別。Kotlin反射API並不是Java反射API的替代方案。同時,Kotlin反射API沒有僅侷限於Kotlin類,可以使用同樣的API訪問用任何JVM語言寫的類。

類引用

最基本的反射功能是獲取 Kotlin 類的執行時引用。

val daqiKotlinClass = daqiKotlin::class
複製程式碼

      Kotlin的類引用實際是KClass 型別的值。如果想獲取Java類引用,可以在KClass 類例項上使用.java屬性獲取。

對於Java類引用,可以使用.kotlin將其轉換為Kotlin類引用

val daqiKotlinJavaClass = daqiKotlin::class.java
val daqiKotlinaClass = daqiKotlinJavaClass.kotlin
複製程式碼
屬性或函式 含義
memberProperties 此類及其所有超類中宣告的非擴充套件屬性
memberFunctions 此類及其所有超類中宣告的非擴充套件非靜態函式
members 此類及其所有超類中宣告的非擴充套件非靜態函式和屬性,不包括建構函式
constructors 全部建構函式
isFinal 是否是final類
isOpen 是否是open類
isAbstract 是否是抽象類
isSealed 是否是密封類
isData 是否是資料類
isInner 是否是內部類
isCompanion 是否是伴生物件

引入Kotlin反射

      在 Java 平臺上,使用反射功能所需的執行時元件作為單獨的 JAR 檔案(kotlin-reflect.jar)分發。這樣做是為了減少不使用反射功能的應用程式所需的執行時庫的大小。如果你需要使用反射,請確保該 .jar檔案新增到專案的 classpath 中。

在Gradle中新增Kotlin反射jar包(具體可看這裡):

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    
    //新增Kotlin反射jar包
    implementation "org.jetbrains.kotlin:kotlin-reflect"
    testImplementation "org.jetbrains.kotlin:kotlin-test"
    testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
}
複製程式碼

KCallable

      在Kotlin的類引用KClass裡,屬性和函式都是存放在KCallable集合members中,而建構函式儲存在KFunction集合constructors中。KFunction的超介面是KCallable,也就是說KCallable是函式(包括建構函式)和屬性的超介面。

KCallable中宣告瞭call方法,允許你呼叫對應的函式或者對應屬性的getter:

public actual interface KCallable<out R> : KAnnotatedElement {
    public fun call(vararg args: Any?): R
    .....
}
複製程式碼

函式KFunction

      學習lambda的時候,學習過方法引用,使用::操作符引用方法,同時會返回一個KFunction物件。

      對於KFunction物件,可以使用KCallable#call方法來呼叫被引用的函式。但::daqi同時也是型別KFunctionN的物件(N代表具體的引數數量,與lambda的Function類似),可以通過invoke方法呼叫該引用函式。(由於該invoke方法是一個openator方法,按照約定可以使用()替代呼叫invoke方法)

fun main(){
    val func = ::daqi
    func.invoke("")
    func.call("")
}

fun daqi(name:String){
}
複製程式碼

      KCallable#callKFunctionN#invoke的區別在於,KFunctionN#invoke的形參型別和返回值型別是可以確定的,編譯器可以做檢查。如果實參型別和數量與invoke不一致,編譯器不會編譯通過。而KCallable#call對所有型別都有效,也意味著需要開發者進行匹配對應的實參型別和數量,否則執行時會得到異常。

每一個KFunctionN型別都繼承了KFunction並加上一個額外的擁有數量剛好引數的invoke。例如:KFunction2宣告瞭openator fun invoke(p1:P1,p2:P2):RKFunctionN型別屬於合成的編譯器生成型別,不能在包kotlin.reflect中找到它們的宣告。意味著可以使用任意數量引數的函式介面。

屬性KProperty

      KProperty與Java的Field不同,它通稱代表相應的GetterSetter,而Java的Field通常指欄位本身。

KProperty可以表示任何屬性,而它的子類KMutableProperty可以表示一個var宣告的可變變數。

      KProperty的超型別也是KCallable,意味著當獲取該屬性時,也可以使用KCallable#callKCallable#call會呼叫該屬性的getter。但Kotlin的屬性介面提供了一個更好的獲取屬性值的方式:get方法。

  • KProperty0<out R>提供的是一個無參的get方法,其型別引數代表屬性的型別;

      例如頂層屬性,直接使用::操作符引用。因為無需接收者,其屬性引用的型別為 KProperty0.

val name = "daqi"

fun main(){
    val property = ::name
    property.get()
    property.call()
}
複製程式碼
  • KProperty1<T, out R>提供一個需要接收者的get方法,其一個型別引數代表接收者的型別,第二個型別引數代表屬性的型別;

      例如 成員屬性 或 擴充套件屬性 ,需要一個具體的接收者例項,其屬性引用的型別為 KProperty1

fun main(){
    val property = daqiKotlin::name
    val daqi = daqiKotlin()
    property.get(daqi)
}

class daqiKotlin{
    val name = ""
}
複製程式碼

屬性引用只能用於定義在外層或類中的屬性,而不能用於區域性變數。

      KMutableProperty作為KProperty子類,除了提供對應get方法外,還提供了set方法。

fun main(){
    val property = daqiKotlin::name
    val daqi = daqiKotlin()
    property.get(daqi)
    property.set(daqi,"")
    property.call(daqi)
}

class daqiKotlin{
    var name = ""
}
複製程式碼

KMutableProperty例項的call方法仍然是呼叫該屬性的getter。

最後

      Kotlin基礎知識歸納系列到本章結束,這一路過來自己也弄清楚了很多概念,Kotlin基礎知識更牢固了。同時也深知自己文筆粗曠,往後會一步步改進的,讓文章的讀者更容易理解文章內容。

最後推薦3篇很不錯的Kotlin文章:

參考資料:

android Kotlin系列:

Kotlin知識歸納(一) —— 基礎語法

Kotlin知識歸納(二) —— 讓函式更好呼叫

Kotlin知識歸納(三) —— 頂層成員與擴充套件

Kotlin知識歸納(四) —— 介面和類

Kotlin知識歸納(五) —— Lambda

Kotlin知識歸納(六) —— 型別系統

Kotlin知識歸納(七) —— 集合

Kotlin知識歸納(八) —— 序列

Kotlin知識歸納(九) —— 約定

Kotlin知識歸納(十) —— 委託

Kotlin知識歸納(十一) —— 高階函式

Kotlin知識歸納(十二) —— 泛型

Kotlin知識歸納(十三) —— 註解

Kotlin知識歸納(十四) —— 反射

Kotlin知識歸納(十四) —— 反射

相關文章