反射是這樣的一組語言和庫功能,它允許在運行時自省你的程序的結(jié)構(gòu)。
Kotlin 讓語言中的函數(shù)和屬性做為一等公民、并對其自省(即在運行時獲悉
一個名稱或者一個屬性或函數(shù)的類型)與簡單地使用函數(shù)式或響應(yīng)式風格緊密相關(guān)。
在 Java 平臺上,使用反射功能所需的運行時組件作為單獨的
JAR 文件(kotlin-reflect.jar
)分發(fā)。這樣做是為了減少不使用反射功能的應(yīng)用程序所需的
運行時庫的大小。如果你需要使用反射,請確保該 .jar文件添加到項目的
classpath 中。
最基本的反射功能是獲取 Kotlin 類的運行時引用。要獲取對
靜態(tài)已知的 Kotlin 類的引用,可以使用 類字面值 語法:
val c = MyClass::class
該引用是 KClass 類型的值。
請注意,Kotlin 類引用與 Java 類引用不同。要獲得 Java 類引用,
請在 KClass
實例上使用 .java
屬性。
通過使用對象作為接收者,可以用相同的 ::class
語法獲取指定對象的類的引用:
val widget: Widget = ……
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
你可以獲取對象的精確類的引用,例如 GoodWidget
或 BadWidget
,盡管接收者表達式的類型是 Widget
。
當我們有一個命名函數(shù)聲明如下:
fun isOdd(x: Int) = x % 2 != 0
我們可以很容易地直接調(diào)用它(isOdd(5)
),但是我們也可以把它作為一個值傳遞。例如傳給另一個函數(shù)。
為此,我們使用 ::
操作符:
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 輸出 [1, 3]
這里 ::isOdd
是函數(shù)類型 (Int) -> Boolean
的一個值。
當上下文中已知函數(shù)期望的類型時,::
可以用于重載函數(shù)。
例如:
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)
如果我們需要使用類的成員函數(shù)或擴展函數(shù),它需要是限定的。
例如 String::toCharArray
為類型 String
提供了一個擴展函數(shù):String.() -> CharArray
。
考慮以下函數(shù):
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
它返回一個傳給它的兩個函數(shù)的組合:compose(f, g) = f(g(*))
。
現(xiàn)在,你可以將其應(yīng)用于可調(diào)用引用:
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
屬性來獲取屬性名。更多信息請參見
關(guān)于 KProperty
類的文檔。
對于可變屬性,例如 var y = 1
,::y
返回 KMutableProperty<Int>
類型的一個值,
該類型有一個 set()
方法。
屬性引用可以用在不需要參數(shù)的函數(shù)處:
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
反射對象之間映射(參見 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"
}
要獲得對應(yīng)于 Java 類的 Kotlin 類,請使用 .kotlin
擴展屬性:
fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin
構(gòu)造函數(shù)可以像方法和屬性那樣引用。他們可以用于期待這樣的函數(shù)類型對象的任何
地方:它與該構(gòu)造函數(shù)接受相同參數(shù)并且返回相應(yīng)類型的對象。
通過使用 ::
操作符并添加類名來引用構(gòu)造函數(shù)??紤]下面的函數(shù),
它期待一個無參并返回 Foo
類型的函數(shù)參數(shù):
class Foo
fun function(factory: () -> Foo) {
val x: Foo = factory()
}
使用 ::Foo
,類 Foo 的零參數(shù)構(gòu)造函數(shù),我們可以這樣簡單地調(diào)用它:
function(::Foo)
你可以引用特定對象的實例方法。
val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // 輸出“true”
val isNumber = numberRegex::matches
println(isNumber("29")) // 輸出“true”
取代直接調(diào)用方法 matches
的是我們存儲其引用。
這樣的引用會綁定到其接收者上。
它可以直接調(diào)用(如上例所示)或者用于任何期待一個函數(shù)類型表達式的時候:
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches)) // 輸出“[124]”
比較綁定的類型和相應(yīng)的未綁定類型的引用。
綁定的可調(diào)用引用有其接收者“附加”到其上,因此接收者的類型不再是參數(shù):
val isNumber: (CharSequence) -> Boolean = numberRegex::matches
val matches: (Regex, CharSequence) -> Boolean = Regex::matches
屬性引用也可以綁定:
val prop = "abc"::length
println(prop.get()) // 輸出“3”