Kotlin反射的使用

反射

反射(Reflection)是程序的自我分析能力,通过反射机制能够动态读取一个类的信息,可以确定类中有哪些函数、构造函数以及属性,并且能够在运行时动态加载类。

在Kotlin中有两种方式来实现反射的功能。一种是调用Java的反射包java.lang.reflect下面的API,另外一种方式就是直接调用Kotlin语言提供的kotlin.reflect包下面的API。

因为反射功能并非所有编程场景都用到,所以Kotlin把kotlin.reflect包的实现放到了单独的kotlin-reflect.jar里面。

如果需要使用Kotlin的反射功能,以Gradle为例,需要在build.gradle配置文件中添加以下依赖:

compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

Kotlin反射API

Kotlin反射API主要来自于kotlin.reflect、kotlin.reflect.full和kotlin.reflect.jvm包。
其中kotlin.reflect和kotlin.reflect.full是主要的Kotlin反射API,而kotlin.reflect.jvm包主要用于Kotlin反射和Java反射的互操作。

 
Kotlin反射API类的层次结构图:

kotlin.reflect包是Kotlin反射核心API,它们都是接口。

类说明:

  • KClass:表示一个具有反射功能的类。
  • KParameter:表示一个具有反射功能的可传递给函数或属性的参数。
  • KCallable:表示具有反射功能的可调用实体,包括属性和函数,它的直接子接口有KProperty和KFunction。
  • KFunction:表示一个具有反射功能的函数。
  • KProperty:表示一个具有反射功能的属性。
  • KMutableProperty:表示一个具有反射功能的使用var声明的属性。

类引用

对类的引用是通过KClass实现的,KClass是实现反射的关键所在,KClass的一个实例表示对类的引用。
在程序代码中引用类使用 :: 运算符,引用类有两种形式:类名::class和对象::class,它们获取的都是相同的KClass实例。

引用类的示例代码如下:

//1.获得“类名::class”引用类
val clz1 = Int::class
val clz2 = Person::class
val person = Person("Tom")
//2.获得“对象::class”引用类 (Kotlin 1.1 绑定的类引用)
val clz3 = person::class

注意:Kotlin 类引用与 Java 类引用不同。要获得 Java 类引用, 请在 KClass 实例上使用 .java 属性或者使用类对象的javaClass扩展属性。

val person = Person("Alice", 29)
//返回一个KClass<Person>的实例
val kClass = person.javaClass.kotlin
println(kClass === Person::class) //true
val jClass = kClass.java
val jClass2 = person.javaClass
println(jClass===jClass2) //true

可调用引用

函数、属性以及构造函数的引用,除了作为自省程序结构外, 还可以用于调用或者用作函数类型的实例。
所有可调用引用的公共超类型是 KCallable, 其中 R 是返回值类型,对于属性是属性类型,对于构造函数是所构造类型。

函数引用

通过反射调用函数需要KFunction实例,KFunction实例可以通过两种方式获得:一个是函数引用;另一个是通过KClass提供的API获得KFunction实例。
函数引用使用 :: 运算符,可以引用顶层函数也可引用类中成员函数。

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

我们可以直接调用它(isOdd(5)),但是我们也可以将其作为一个函数类型的值,例如将其传给另一个函数。为此,我们使用 :: 操作符:

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

fun main() {
    //sampleStart
    val numbers = listOf(1, 2, 3)
    println(numbers.filter(::isOdd)) //[1,3]
    //sampleEnd
}

这里 ::isOdd 是函数类型 (Int) -> Boolean 的一个值。
函数引用属于 KFunction 的子类型之一,取决于参数个数,例如 KFunction3<T1, T2, T3, R>。
因此 ::isOdd表达式的类型也是KFunction1<Int, Boolean>,它包含了形参类型和返回类型的信息,1表示这个函数接收一个形参,Int表示形参类型,Boolean表示函数返回类型。
 
像KFunctionN这种类型称为合成的编译器生成类型,你不会在包kotlin.reflect中找到它们的声明。
每个KFunctionN类型都继承了KFunction并额外加上了一个成员方法invoke,可以使用KFunctionN接口的invoke方法来调用对应函数。

fun sum(x: Int, y: Int) = x + y

fun main() {
    val sumFun: (Int, Int) -> Int = ::sum
    println(sumFun(1, 2))

    //一个函数类型的变量(函数引用)属于 KFunction<out R> 的子类型
    val kFunction2: KFunction2<Int, Int, Int> = ::sum
    println(kFunction2.invoke(1, 2) + kFunction2(3, 4)) //10

    //KFunction接口继承自KCallable和Function
    val sumFun2 = ::sum
    /**
     * 调用KCallable接口的call方法,传入可变参数,因为call方法是对所有类型都有效的通用手段,但它不提供类型安全性。
     * 如果用错误数量的实参去调用函数,就会抛出一个运行时异常IllegalArgumentException
     */
    println(sumFun2.call(3, 6)) //调用KCallable接口的call方法
    println(sumFun2.invoke(2, 5)) //调用Function接口的invoke方法
    //会被编译成sumFun2.invoke(1, 3)
    println(sumFun2(1, 3))
}

当上下文中已知函数期望的类型时,:: 可以用于重载函数。 例如:

fun main() {
    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。

示例1:

//String类里有2个toCharArray函数,需要指定类型限定:(String) -> CharArray
val toCharArrayFun: (String) -> CharArray = String::toCharArray
val arr = toCharArrayFun.invoke("abcd") // 或者使用toCharArrayFun("abcd")
arr.forEach {
    println(it)
}

示例2:

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

fun main() {
    val calc = Calc()
    //isOddFun类型限定
    val isOddFun: (Calc, String) -> Boolean = Calc::isOdd
    println(isOddFun(calc, "tove")) //true

    val isOddFun2: (String) -> Boolean = calc::isOdd
    println(isOddFun2("brillig")) //true
}

 
获取成员函数和扩展函数的引用

import kotlin.reflect.full.createInstance
import kotlin.reflect.full.functions

class Person {

    var name: String? = null
    var age: Int = 0

    //成员函数
    fun setNameAndAge(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    override fun toString(): String {
        return "Person [name=$name, age=$age]"
    }
}

//Person扩展函数
fun Person.getNameAndAge() = "name=$name,age=$age"

fun main() {
    val clz = Person::class
    val person = clz.createInstance()

    clz.functions.forEach { println(it.name) }
    //KClass提供的API获得KFunction实例
    val pfn1 = clz.functions.first()
    pfn1.call(person, "Tom", 20)
    println(person)

    val pfn2 = Person::setNameAndAge //KFunction3<Person,String,Int,Unit>

    pfn2(person, "Tony", 18) // Person [name=Tom, age=20]
    println(person) // Person [name=Tony, age=18]
    pfn2.call(person, "Ben", 28)
    println(person) // Person [name=Ben, age=28]
    pfn2.invoke(person,"Catty",16)
    println(person) //Person [name=Catty, age=16]

    val pfn3 = Person::getNameAndAge //KFunction3<Person,String>
    println(pfn3(person))
}

属性引用

通过反射调用属性需要KProperty实例。获得KProperty实例可以通过两种方式获得:一个是属性引用;另一个是通过KClass提供的API获得KProperty实例。
获得属性引用也可以使用 :: 运算符:

val x = 1

fun main() {
    println(::x.get()) //1
    println(::x.name) //x
}

表达式 ::x 求值为 KProperty 类型的属性对象,它允许我们使用 get() 读取它的值,或者使用 name 属性来获取属性名。
 
对于可变属性,例如 var y = 1,::y 返回 KMutableProperty 类型的一个值, 该类型有一个 set() 方法。

var y = 1

fun main() {
    ::y.set(2)
    println(y) //2
}
var counter = 0
fun main() {
    val kProperty = ::counter //顶层属性的引用由KProperty0的实例表示,这里类型是KMutableProperty0<Int>
    kProperty.setter.call(21)  //通过KCallable的call方法
    kProperty.set(22)
    println(kProperty.call()) //在KProperty实例上调用call方法会调用该属性的getter方法
    println(kProperty.getter.call())
    println(kProperty.get())//
}

 
属性引用可用于期望具有一个参数的函数:

val strs = listOf("a", "bc", "def")

println(strs.map { string ->
    string.length
})
println(strs.map { it.length }) //一个参数可以使用it替代
println(strs.map(String::length)) //[1, 2, 3]

 
访问属于类的成员的属性:

import kotlin.reflect.full.memberProperties

fun main() {
    class A(val p: Int)

    val a = A(1)
    val prop = A::p //成员属性的引用由KProperty1的实例表示,这里类型是KProperty1<A,Int>
    //调用call方法会调用该属性的getter
    println(prop.call(a)) //1
    println(prop.getter.call(a)) //1
    //更好获取属性值的方式:通过get方法
    println(prop.get(a)) //1

    val clz = A::class
    //通过KClass提供的API获得KProperty实例
    val prop1 = clz.memberProperties.first()
    println(prop1.get(a)) //1
}

 
对于扩展属性:

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

fun main() {
    println(String::lastChar.get("abc")) //c
    println("abc".lastChar) //c
}

与 Java 反射的互操作性

在Java平台上,标准库包含反射类的扩展,它提供了与 Java 反射对象之间映射(参见 kotlin.reflect.jvm 包)
例如,为一个 Kotlin 属性获取一个 Java 的 getter/setter 方法或者幕后字段

import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.javaSetter

class A(var p: Int)

fun main() {
    println(A::p.javaGetter) // 输出 "public final int A.getP()"
    println(A::p.javaField)  // 输出 "private final int A.p"

    println(A::p.javaSetter) // 输出 "public final void A.setP(int)"
    println(A::p.javaClass)  // 输出 "class ReflectCompatKt$main$4",类型是Class
    //要获得对应于 Java 类的 Kotlin 类,请使用 .kotlin 扩展属性
    println(A::p.javaClass.kotlin)  // 输出 "class ReflectCompatKt$main$5",类型是KClass
}

构造函数引用

构造函数可以像方法和属性那样引用。它与该构造函数接受相同参数并且返回相应类型的对象。
通过使用 :: 操作符并添加类名来引用构造函数。
示例1:

class Foo

fun function(factory: () -> Foo) { //factory:一个无参并返回 Foo 类型的函数参数
    val x: Foo = factory()
    println(x) // Foo@4617c264
}

fun main() {
    function(::Foo)
}

示例2:

data class Person(val name: String, val age: Int)

//扩展函数
fun Person.isAdult() = age >= 18

//构造方法引用存储
val createPerson = ::Person
/**
 * 尽管isAdult不是Person类的成员,还是可以通过引用访问它,这和访问实例的成员是一样的
 */
val predicate = Person::isAdult

fun main() {
    //构造方法引用 延迟执行创建类实例动作
    val p = createPerson("TomCat", 22)
    println(p) //Person(name=TomCat, age=22)
    println(predicate(p)) //true
}

绑定的函数与属性引用(自 1.1 起)

通过对象::函数名(或属性名)把函数(或属性)的引用绑定在某个对象上,然后通过这个引用就能访问特定对象的函数或属性。

引用特定对象的实例方法

fun main() {
    val numberRegex = "\\d+".toRegex()
    println(numberRegex.matches("29"))

    //val matches: (Regex, CharSequence) -> Boolean = Regex::matches
    val matches = Regex::matches //Regex的matches函数引用
    println(matches(numberRegex, "29")) //需要传入numberRegex对象参数

    //val isNumber: (CharSequence) -> Boolean = numberRegex::matches
    val isNumber = numberRegex::matches //把Regex的matches函数引用绑定到numberRegex对象上
    println(isNumber("29")) //不需要传入numberRegex对象参数

    //绑定的引用可以用于任何只需要一个函数类型表达式的场景
    val strings = listOf("abc", "124", "a70")
    println(strings.filter(numberRegex::matches)) //[124]
}

引用特定对象的属性

示例1:

fun main() {
    val prop = "abc"::length
    println(prop.get()) //3
}

示例2:

data class Person(val name: String, val age: Int)

//构造方法引用存储
val createPerson = ::Person

fun main() {
    //构造方法引用 延迟执行创建类实例动作
    val p = createPerson("TomCat", 22)

    //绑定成员引用
    val dmitrysAgeFunction = p::age  //KProperty0<Int>
    println(dmitrysAgeFunction.get()) //get不需要接收者对象参数
    println(dmitrysAgeFunction())//22

    val dmitrysAgeProperty = Person::age //KProperty1<Person, Int>
    println(dmitrysAgeProperty.get(p)) //22
}

引用特定对象的构造函数

inner 类的构造函数的绑定的可调用引用可通过提供外部类的实例来获得:

class Outer {
    inner class Inner
    class Nest
}

fun main() {
    //创建外部类实例
    val o = Outer()
    val boundInnerCtor = o::Inner
    println(boundInnerCtor.invoke()) //Outer$Inner@2626b418
    println(boundInnerCtor()) //Outer$Inner@5a07e868

    //嵌套类构造方法的引用
    val nestCtor = Outer::Nest
    println(nestCtor()) //Outer$Nest@2626b418
}

参考:Kotlin Reflection


   转载规则


《Kotlin反射的使用》 congwiny 采用 知识共享署名 4.0 国际许可协议 进行许可。
评论
 上一篇
Kotlin操作符重载 Kotlin操作符重载
操作符操作符优先级Kotlin操作符的优先级如下图所示: 为实现这些的操作符,Kotlin为二元操作符左侧的类型和一元操作符的参数类型,提供了相应的函数或扩展函数。 例如在kotlin/core/builtins/native/kotlin
2019-09-02
下一篇 
如何使用 Kotlin 作用域函数 如何使用 Kotlin 作用域函数
作用域函数Kotlin 提供了一系列用来在【给定对象上下文】中执行代码块的函数。当您在提供了lambda表达式的对象上调用此类函数时,它会形成一个临时范围。在此范围内,您可以在不使用其名称的情况下访问该对象。常用到的作用域函数有5个:let
2019-08-30
  目录