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) } 來得更簡潔。

實際應用場景

反射通常用於開發框架或通用工具庫,例如:

  1. JSON Serialization: 像 Gson 或 Moshi 這樣的庫,使用反射來遍歷物件的所有屬性,將其轉換為 JSON 字串。
  2. Dependency Injection: 像 Spring 或 Koin,使用反射來實例化類別並注入依賴。
  3. ORM: 資料庫框架使用反射將資料庫欄位映射到物件屬性。

小提醒

雖然反射功能強大,但有幾個缺點需要注意:

  • 效能開銷:反射操作通常比直接呼叫程式碼慢,因為涉及動態解析。在效能敏感的迴圈中應謹慎使用。
  • ProGuard / R8 混淆:如果你的專案有開啟程式碼混淆(例如 Android App 發布時),反射查找的類別或屬性名稱可能會被改名,導致執行期錯誤。需要特別設定 proguard-rules.pro 來保留被反射使用的類別。
對於大多數的業務邏輯開發(App 頁面邏輯、後端 API 邏輯),你很少需要直接寫反射程式碼。通常是「使用」那些依賴反射的框架。