关于反射的概念和java的反射的使用,可以参考文章:java反射。建议看完参考文章后再看本篇文章,因为本篇文章会基于java的反射对比kotlin的反射。
kotlin的 KClass
和java的 Class
可以看作同一个含义的类型,并且可以通过 .java
和 .kotlin
方法在 KClass
和 Class
之间互相转化
kotlin的 KCallable
和java的 AccessibleObject
都可以理解为可调用元素。java中构造方法 Constructor
为一个独立的类型,而kotlin则统一作为 KFunction
处理
kotlin的 KProperty
和java的 Field
不太相同。java的 Field
通常仅仅指字段本身,Field
具备了getter和setter;kotlin并没有字段的概念,她将属性分为了 KProperty
和 KMutableProperty
,当变量声明为 val
时,getter即为 KProperty
,当变量声明为 var
时,getter和setter即为 KProperty
和 KMutableProperty
通过上面了解了kotlin和java在反射的基本结构对比,接下来简单了解下kotlin的反射该怎么使用,同样会对比java反射的使用。
java的反射操作步骤大概可以总结为以下几点:
获取Class
通过Class获取obj
通过obj反射获取对象的成员、方法等
kotlin的反射操作步骤大致和java的相同,因为反射基本结构不同,所以在代码实现上有稍微的不同。
在这里提供一个测试用例,后续反射的使用讲解会根据用例操作:
class Person {
var instanceField: String = "instanceFieldValue"
constructor()
constructor(instanceField: String) {
this.instanceField = instanceField
}
fun invokeMethod() {
println("invokeMethod")
}
fun invokeMethodWithParam(param: String) {
println("invokeMethodWithParam, param = $param")
}
companion object {
var companionField: String = "companionFieldValue"
fun invokeCompanionMethod() {
println("invokeCompanionMethod")
}
fun invokeCompanionMethodWithParam(param: String) {
println("invokeCompanionMethodWithParam, param = $param")
}
}
}
java的 Class
和kotlin的 KClass
是可以互相转换的,主要有以下几种方式:
Person().javaClass // Class
Person()::class.java // Class
Person::class.java // Class
Person().javaClass.kotlin // KClass
Person::class // KClass
在java可以通过 Class.forName()
获取到一个 Class
对象,kotlin如果想要通过类名获取 KClass
对象,还是需要借助java该方法然后做一次转换:
val personClass = Class.forName("com.example.demo.Person").kotlin
println(personClass)
输出:
class Person
java的Class加载方式同样适用于kotlin,具体参考上面列出的java反射的文章,这里不再说明:
class Person {
init {
println("init block initialized...")
}
constructor() {
println("constructor initialized...")
}
companion object {
init {
println("companion init block initialized...")
}
}
}
fun main() {
println(Person::class)
}
输出:
class Person
fun main() {
println(Class.forName("com.example.demo.Person").kotlin)
}
输出:
static init initialized...
class Person
fun main() {
println(Person().javaClass)
}
输出:
static init initialized...
init initialized...
constructor initialized...
class Person
同样分成两种方式获取。
// 和java的Person.class.newInstance()相同,类需要提供无参构造
val personClass = Person::class
val obj = personClass.createInstance()
在kotlin要获取带有参数的构造函数不像java那么便捷,需要手动遍历所有构造函数,比如通过判断构造函数的参数数量反射创建对象:
val personClass = Person::class
val hasParamConstructor = personClass.constructors.find {
it.parameters.size == 1 }
val obj = hasParamConstructor?.call("constructorInitializeFieldValue")
// 验证是否调用了带参数的构造函数创建对象
personClass.memberProperties.find {
property ->
property.name == "instanceField"
}?.let {
targetField ->
println(targetField.getter.call(obj))
}
kotlin是有分主构造函数和次构造函数的,在反射时kotlin提供了 primaryConstructor
方便的获取主构造函数:
class Resource(val id: Long) {
private var value: String? = null
constructor(): this(1L)
constructor(id: Long, value: String): this(id) {
this.value = value
}
}
fun main() {
val resourceClass = Resource::class
val obj = resourceClass.primaryConstructor?.call(2)
// 验证是否调用了主构造函数
println((obj as Resource).id)
}
val personClass = Class.forName("com.example.demo.Person").kotlin
val obj = personClass.createInstance()
或
val personClass = Class.forName("com.example.demo.Person").kotlin
val hasParamConstructor = personClass.constructors.find {
it.parameters.size == 1 }
val obj = hasParamConstructor?.call("constructorInitializeFieldValue")
// 验证是否调用了带参数的构造函数创建对象
personClass.memberProperties.find {
property ->
property.name == "instanceField"
}?.let {
targetField ->
println(targetField.getter.call(obj))
}
当使用反射访问的成员和函数为私有时,与java相同需要设置 isAccessable
为 true
取消权限控制检查。
fun main() {
val personClass = Class.forName("com.example.demo.Person").kotlin
val obj = personClass.createInstance()
// 访问非静态成员变量
personClass.declaredMemberProperties.find {
it.name == "instanceField" }?.let {
// it.getter.isAccessible = true
println((it as KProperty<*>).getter.call(obj))
if (it is KMutableProperty<*>) {
println(it.setter.call(obj, "modifyInstanceFieldValue"))
println(it.getter.call(obj))
}
}
// 访问非静态函数
personClass.declaredFunctions.forEach {
when (it.name) {
"invokeMethod" -> it.call(obj)
"invokeMethodWithParam" -> it.call(obj, "my param")
}
}
// 访问companion object的成员变量
val companionObj = personClass.companionObjectInstance
personClass.companionObject?.declaredMemberProperties?.find {
it.name == "companionField" }?.let {
println((it as KProperty<*>).getter.call(companionObj))
if (it is KMutableProperty<*>) {
it.setter.call(companionObj, "modifyStaticFieldValue")
println(it.getter.call(companionObj))
}
}
// 访问companion object的函数
personClass.companionObject?.declaredFunctions?.forEach {
when (it.name) {
"invokeCompanionMethod" -> it.call(companionObj)
"invokeCompanionMethodWithParam" -> it.call(companionObj, "my param")
}
}
}
通过上面代码可以发现,在需要反射的 KClass
对象类型无法直接访问时,访问变量会通过getter或setter获取到 KProperty
或 KMutableProperty
,函数则会通过 KFunction
,然后调用 call()
实现反射。
尽管kotlin的反射和java非常相似,但是它仍旧有一些独特的地方,主要是集中在kotlin中独有,java没有与之对应的特性。KClass
的特别属性或者函数如下表:
var kClass = Person::class
// Boolean
kClass.isCompanion
// Boolean
kClass.isData
// Boolean
kClass.isSealed
// T?
kClass.objectInstance
// Any?
kClass.companionObjectInstance
// Collection>
kClass.declaredMemberExtensionFunctions
// Collection>
kClass.declaredMemberExtensionProperties
// Collection>
kClass.memberExtensionFunctions
// Collection>
kClass.memberExtensionProperties
// KType
kClass.starProjectedType
属性或函数名称 | 含义 |
---|---|
isCompanion | 是否伴生对象 |
isData | 是否数据类 |
isSealed | 是否密封类 |
objectInstance | object实例(如果是object) |
companionObjectInstance | 伴生对象实例 |
declaredMemberExtensionFunctions | 扩展函数 |
declaredMemberExtensionProperties | 扩展属性 |
memberExtensionFunctions | 本类及超类扩展函数 |
memberExtensionProperties | 本类及超类扩展属性 |
starProjectedType | 泛型通配类型 |
用一个示例演示kotlin特有的属性和函数:
sealed class Nat {
companion object {
object Zero : Nat()
}
val Companion._0
get() = Zero
// Succ对象的扩展函数
fun <A : Nat> Succ<A>.proceed(): A {
return this.prev
}
}
data class Succ<N : Nat>(val prev: N) : Nat()
// Nat对象的扩展函数
fun <A : Nat> Nat.plus(other: A): Nat = when {
other is Succ<*> -> Succ(this.plus(other.prev))
else -> this
}
// true
println(Nat.Companion::class.isCompanion)
// true
println(Nat::class.isSealed)
// Nat$Companion@43ee72e6
println(Nat.Companion::class.objectInstance)
// Nat$Companion@43ee72e6
println(Nat::class.companionObjectInstance)
// fun Nat.(Succ.)proceed(): A.name
Nat::class.declaredMemberExtensionFunctions.map {
println("Nat::class.declaredMemberExtensionFunctions $it.name")
}
// _0
Nat::class.declaredMemberExtensionProperties.map {
println("Nat::class.declaredMemberExtensionProperties ${it.name}")
}
// proceed
Nat::class.memberExtensionFunctions.map {
println("Nat::class.memberExtensionFunction ${it.name}")
}
// _0
Nat::class.memberExtensionProperties.map {
println("Nat::class.memberExtensionProperties ${it.name}")
}
// print nothing
Succ::class.declaredMemberExtensionFunctions.map {
println("Succ::class.declaredMemberExtensionFunction ${it.name}")
}
// print nothing
Succ::class.declaredMemberExtensionProperties.map {
println("Succ::class.declaredMemberExtensionProperties ${it.name}")
}
// proceed
Succ::class.memberExtensionFunctions.map {
println("Succ::class.memberExtensionFunctions ${it.name}")
}
// _0
Succ::class.memberExtensionProperties.map {
println("Succ::class.memberExtensionProperties ${it.name}")
}
// Succ<*>
println(Succ::class.starProjectedType)
declaredMemberExtensionFunctions()
这类函数返回的结果指的是这个类中声明的扩展函数,而不是在其他位置声明的本类的扩展函数。比如上面例子中 Nat::class.declaredMemberExtensionFunctions()
返回了类中定义的 Succ.proceed()
,没有返回定义在类外的 Nat.plus()
。这类方法其实有点”鸡肋“,更多时候我们希望获得的是此类的扩展方法。
kotlin把Class的属性(Property)、函数(Function)甚至构造函数都看作 KCallable
,因为它们是可调用的,它们都是Class的成员。
在上面的例子中,获取kotlin的 KProperty
、KMutableProperty
和 KFunction
分别使用了 KClass.declaredMemberProperties
和 KClass.declaredFunctions
区分获取,既然kotlin将 KProperty
和 KFunction
都归类到 KCallable
,同样也有一个函数可以同时获取到它们即 KClass.members
,它的返回值是一个 Collection
companion object` 内声明的成员和函数):
val personClass = Class.forName("com.example.demo.Person").kotlin
personClass.members.forEach {
println(it.name)
}
输出:
instanceField
invokeMethod
invokeMethodWithParam
equals
hashCode
toString
KCallable
其他属性如下:
API | 描述 |
---|---|
isAbstract: Boolean |
此KCallable是否为抽象的 |
isFinal: Boolean | 此KCallable是否为final |
isOpen: Boolean | 此KCallable是否为open |
name: String | 此KCallable的名称 |
parameters: List |
调用KCallable需要的参数 |
returnType: KType | 此KCallable的返回类型 |
typeParameters: List |
此KCallable的类型参数 |
visibility: KVisibility? | 此KCallable的可见性 |
call(vararg args: Any?): R | 给定参数调用此KCallable |
kotlin把参数 分为3个类别,分别是函数的参数 KParameter
、函数的返回值 KType
和类型参数 KTypeParameter
。
使用 KCallable.parameters
即可获取一个 List
,它代表的是函数(包括扩展函数)的参数。
API | 描述 |
---|---|
index: Int | 返回该参数在参数列表里面的index |
isOptional: Boolean | 该参数是否为Optional |
isVararg: Boolean | 该参数是否为vararg |
kind: Kind | 该参数的kind |
name: String? | 该参数的名称 |
type: KType | 该参数的类型 |
fun main() {
val personClass = Class.forName("com.example.demo.Person").kotlin
personClass.members.forEach {
print("${it.name} -> ")
it.parameters.forEach {
parameter ->
print("${parameter.type}")
}
println()
}
}
输出:
instanceField -> com.example.demo.Person
invokeMethod -> com.example.demo.Person
invokeMethodWithParam -> com.example.demo.Personkotlin.String
equals -> com.example.demo.Personkotlin.Any?
hashCode -> com.example.demo.Person
toString -> com.example.demo.Person
通过上面的运行结果发现,对于属性和无参数的函数(如 instanceField
和 invokeMethod()
),它们都有一个隐藏的参数为类的实例,而对于声明参数的函数(如 invokeMethodWithParam()
),类的实例作为第1个参数,而声明的参数作为后续的参数。
每一个 KCallable
都可以使用 returnType
获取返回值类型,它的结果类型是一个 KType
。
API | 描述 |
---|---|
arguments: List |
该类型的类型参数 |
classifier: KClassifier? | 该类型在类声明层面的类型,如该类型为List |
isMarkedNullable: Boolean | 该类型是否标记为可空类型 |
我们在 Person
添加一个返回值为 List
的方法演示classifier:
class Person {
fun listName(): List<String> {
return listOf("Person1", "Person2")
}
}
fun main() {
val personClass = Class.forName("com.example.demo.Person").kotlin
personClass.members.forEach {
println("${it.name} -> ${it.returnType.classifier}")
}
}
输出:
instanceField -> class kotlin.String
invokeMethod -> class kotlin.Unit
invokeMethodWithParam -> class kotlin.Unit
listName -> class kotlin.collections.List
equals -> class kotlin.Boolean
hashCode -> class kotlin.Int
toString -> class kotlin.String
classifier API其实就是获取该参数在类层面对应的类型,如 Int
为 class kotlin.Int
,List
为 class kotlin.collections.List
。
在 KClass
和 KCallable
中我们可以通过 typeParameters
获取类型参数,它返回的结果是 List
,不存在类型参数时就返回一个空的List。
在 Person
中添加一个带类型参数的方法:
class Person {
fun <A> get(a: A): A {
return a
}
}
fun main() {
val personClass = Class.forName("com.example.demo.Person").kotlin
personClass.members.find {
it.name == "get" }?.let {
println(it.typeParameters)
}
val list = listOf("How")
println(list::class.typeParameters)
}
输出:
[A]
[E]