Kotlin反射

反射是這樣的一組語言和庫功能,它允許在運行時自省你的程序的結構。
Kotlin 讓語言中的函數和屬性做爲一等公民、並對其自省(即在運行時獲悉
一個名稱或者一個屬性或函數的類型)與簡單地使用函數式或響應式風格緊密相關。

在 Java 平臺上,使用反射功能所需的運行時組件作爲單獨的
JAR 文件(kotlin-reflect.jar)分發。這樣做是爲了減少不使用反射功能的應用程序所需的
運行時庫的大小。如果你需要使用反射,請確保該 .jar文件添加到項目的
classpath 中。

類引用

最基本的反射功能是獲取 Kotlin 類的運行時引用。要獲取對
靜態已知的 Kotlin 類的引用,可以使用 類字面值 語法:

val c = MyClass::class

該引用是 KClass 類型的值。

請注意,Kotlin 類引用與 Java 類引用不同。要獲得 Java 類引用,
請在 KClass 實例上使用 .java 屬性。

綁定的類引用(自 1.1 起)

通過使用對象作爲接收者,可以用相同的 ::class 語法獲取指定對象的類的引用:

val widget: Widget = ……
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }

你可以獲取對象的精確類的引用,例如 GoodWidgetBadWidget,儘管接收者表達式的類型是 Widget

函數引用

當我們有一個命名函數聲明如下:

fun isOdd(x: Int) = x % 2 != 0

我們可以很容易地直接調用它(isOdd(5)),但是我們也可以把它作爲一個值傳遞。例如傳給另一個函數。
爲此,我們使用 :: 操作符:

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 輸出 [1, 3]

這裏 ::isOdd 是函數類型 (Int) -> Boolean 的一個值。

當上下文中已知函數期望的類型時,:: 可以用於重載函數。
例如:

fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 引用到 isOdd(x: Int)

或者,你可以通過將方法引用存儲在具有顯式指定類型的變量中來提供必要的上下文:

val predicate: (String) -> Boolean = ::isOdd   // 引用到 isOdd(x: String)

如果我們需要使用類的成員函數或擴展函數,它需要是限定的。
例如 String::toCharArray 爲類型 String 提供了一個擴展函數:String.() -> CharArray

示例:函數組合

考慮以下函數:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}

它返回一個傳給它的兩個函數的組合:compose(f, g) = f(g(*))
現在,你可以將其應用於可調用引用:

fun length(s: String) = s.length

val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")

println(strings.filter(oddLength)) // 輸出 "[a, abc]"

屬性引用

要把屬性作爲 Kotlin中 的一等對象來訪問,我們也可以使用 :: 運算符:

var x = 1

fun main(args: Array<String>) {
    println(::x.get()) // 輸出 "1"
    ::x.set(2)
    println(x)         // 輸出 "2"
}

表達式 ::x 求值爲 KProperty<Int> 類型的屬性對象,它允許我們使用
get() 讀取它的值,或者使用 name 屬性來獲取屬性名。更多信息請參見
關於 KProperty 類的文檔

對於可變屬性,例如 var y = 1::y 返回 KMutableProperty<Int> 類型的一個值,
該類型有一個 set() 方法。

屬性引用可以用在不需要參數的函數處:

val strs = listOf("a", "bc", "def")
println(strs.map(String::length)) // 輸出 [1, 2, 3]

要訪問屬於類的成員的屬性,我們這樣限定它:

class A(val p: Int)

fun main(args: Array<String>) {
    val prop = A::p
    println(prop.get(A(1))) // 輸出 "1"
}

對於擴展屬性:

val String.lastChar: Char
    get() = this[length - 1]

fun main(args: Array<String>) {
    println(String::lastChar.get("abc")) // 輸出 "c"
}

與 Java 反射的互操作性

在Java平臺上,標準庫包含反射類的擴展,它提供了與 Java
反射對象之間映射(參見 kotlin.reflect.jvm 包)。
例如,要查找一個用作 Kotlin 屬性 getter 的 幕後字段或 Java方法,可以這樣寫:

import kotlin.reflect.jvm.*

class A(val p: Int)

fun main(args: Array<String>) {
    println(A::p.javaGetter) // 輸出 "public final int A.getP()"
    println(A::p.javaField)  // 輸出 "private final int A.p"
}

要獲得對應於 Java 類的 Kotlin 類,請使用 .kotlin 擴展屬性:

fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin

構造函數引用

構造函數可以像方法和屬性那樣引用。他們可以用於期待這樣的函數類型對象的任何
地方:它與該構造函數接受相同參數並且返回相應類型的對象。
通過使用 :: 操作符並添加類名來引用構造函數。考慮下面的函數,
它期待一個無參並返回 Foo 類型的函數參數:

class Foo

fun function(factory: () -> Foo) {
    val x: Foo = factory()
}

使用 ::Foo,類 Foo 的零參數構造函數,我們可以這樣簡單地調用它:

function(::Foo)

綁定的函數與屬性引用(自 1.1 起)

你可以引用特定對象的實例方法。

val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // 輸出「true」

val isNumber = numberRegex::matches
println(isNumber("29")) // 輸出「true」

取代直接調用方法 matches 的是我們存儲其引用。
這樣的引用會綁定到其接收者上。
它可以直接調用(如上例所示)或者用於任何期待一個函數類型表達式的時候:

val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches)) // 輸出「[124]」

比較綁定的類型和相應的未綁定類型的引用。
綁定的可調用引用有其接收者「附加」到其上,因此接收者的類型不再是參數:

val isNumber: (CharSequence) -> Boolean = numberRegex::matches

val matches: (Regex, CharSequence) -> Boolean = Regex::matches

屬性引用也可以綁定:

val prop = "abc"::length
println(prop.get())   // 輸出「3」