前序
當在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#call
與KFunctionN#invoke
的區別在於,KFunctionN#invoke
的形參型別和返回值型別是可以確定的,編譯器可以做檢查。如果實參型別和數量與invoke
不一致,編譯器不會編譯通過。而KCallable#call
對所有型別都有效,也意味著需要開發者進行匹配對應的實參型別和數量,否則執行時會得到異常。
每一個
KFunctionN
型別都繼承了KFunction
並加上一個額外的擁有數量剛好引數的invoke。例如:KFunction2宣告瞭openator fun invoke(p1:P1,p2:P2):R
。KFunctionN
型別屬於合成的編譯器生成型別,不能在包kotlin.reflect
中找到它們的宣告。意味著可以使用任意數量引數的函式介面。
屬性KProperty
KProperty
與Java的Field
不同,它通稱代表相應的Getter
和Setter
,而Java的Field
通常指欄位本身。
KProperty
可以表示任何屬性,而它的子類KMutableProperty
可以表示一個var
宣告的可變變數。
KProperty
的超型別也是KCallable
,意味著當獲取該屬性時,也可以使用KCallable#call
,KCallable#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文章:
參考資料:
- 《Kotlin實戰》
- Kotlin官網