Kotlin Reflection (反射)
Reflection (反射) 是一組語言功能與函式庫,允許程式在「執行期 (Runtime)」檢視程式的結構(例如類別、函式、屬性)。
Kotlin 將函數和屬性視為一等公民 (First-class citizen),因此它的反射能力不僅限於類別,還能深入檢視函式與屬性的名稱、型別甚至是 metadata。
設置 (Setup)
Kotlin 的標準庫 (kotlin-stdlib) 僅包含最核心的反射功能。如果需要完整的反射能力(例如透過反射讀取屬性的值),你需要加入 kotlin-reflect 依賴。
Gradle (Groovy):
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.0" // 請使用與你專案相符的 Kotlin 版本
Gradle (Kotlin DSL):
implementation(kotlin("reflect"))
Class References (類別引用)
在 Kotlin 中,最基本的反射操作就是取得一個類別的 Runtimet 引用。我們使用雙冒號 ::class 語法。
val c = MyClass::class
這回傳的是一個 KClass (Kotlin Class) 物件,而不是 Java 的 Class。
Java Class Reference
如果你需要與 Java Reflection API 互動,可以使用 .java 屬性來取得對應的 Java Class:
val c: KClass<MyClass> = MyClass::class
val j: Class<MyClass> = MyClass::class.java
Callable References (可呼叫引用)
我們可以傳遞「函式本身」作為參數,而不是傳遞 Lambda。
Function References
假設我們有一個判斷奇數的函式:
fun isOdd(x: Int) = x % 2 != 0
我們可以直接將它傳遞給 filter,透過 ::isOdd:
val numbers = listOf(1, 2, 3)
// 使用 Lambda
println(numbers.filter { isOdd(it) })
// 使用 Function Reference
println(numbers.filter(::isOdd))
Property References
不僅函式,屬性也可以被引用。::x 會回傳一個 KProperty<T> 物件,讓我們可以讀取它的值或取得它的名稱等資訊。
val x = 1
fun main() {
println(::x.get()) // 輸出 1
println(::x.name) // 輸出 "x"
}
若是可變屬性 (var),則會回傳 KMutableProperty<T>,可以使用 .set() 修改值:
var y = 1
fun main() {
::y.set(2)
println(y) // 輸出 2
}
Constructor References (建構式引用)
就像方法一樣,如果你需要的參數是一個「會產生新物件的函式」,你可以直接傳入建構式的引用。
class Foo
fun function(factory: () -> Foo) {
val x = factory()
}
fun main() {
function(::Foo)
}
Bound References (綁定引用)
從 Kotlin 1.1 開始,我們可以取得「特定物件實體」的參考。
Bound Class Reference
你可以直接對一個「物件實體」使用 ::class,取得它實際的執行期類別:
val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
Bound Callable References
你可以引用特定物件的方法或屬性:
val numberRegex = "\\d+".toRegex()
// 綁定 numberRegex 這個實體的 matches 方法
val isNumber = numberRegex::matches
println(isNumber("29")) // true
這比寫 lambda val isNumber = { str: String -> numberRegex.matches(str) } 來得更簡潔。
實際應用場景
反射通常用於開發框架或通用工具庫,例如:
- JSON Serialization: 像 Gson 或 Moshi 這樣的庫,使用反射來遍歷物件的所有屬性,將其轉換為 JSON 字串。
- Dependency Injection: 像 Spring 或 Koin,使用反射來實例化類別並注入依賴。
- ORM: 資料庫框架使用反射將資料庫欄位映射到物件屬性。
小提醒
雖然反射功能強大,但有幾個缺點需要注意:
- 效能開銷:反射操作通常比直接呼叫程式碼慢,因為涉及動態解析。在效能敏感的迴圈中應謹慎使用。
- ProGuard / R8 混淆:如果你的專案有開啟程式碼混淆(例如 Android App 發布時),反射查找的類別或屬性名稱可能會被改名,導致執行期錯誤。需要特別設定
proguard-rules.pro來保留被反射使用的類別。