Kotlin入门系列:第八章 反射

关于反射的概念和java的反射的使用,可以参考文章:java反射。建议看完参考文章后再看本篇文章,因为本篇文章会基于java的反射对比kotlin的反射。

1 kotlin和java反射基本结构对比

在这里插入图片描述

  • kotlin的 KClass 和java的 Class 可以看作同一个含义的类型,并且可以通过 .java.kotlin 方法在 KClassClass 之间互相转化

  • kotlin的 KCallable 和java的 AccessibleObject 都可以理解为可调用元素。java中构造方法 Constructor 为一个独立的类型,而kotlin则统一作为 KFunction 处理

  • kotlin的 KProperty 和java的 Field 不太相同。java的 Field 通常仅仅指字段本身,Field 具备了getter和setter;kotlin并没有字段的概念,她将属性分为了 KPropertyKMutableProperty ,当变量声明为 val 时,getter即为 KProperty,当变量声明为 var 时,getter和setter即为 KPropertyKMutableProperty

2 kotlin反射的使用

通过上面了解了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")
        }
    }
}

2.1 获取KClass

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

2.2 通过KClass获取obj

同样分成两种方式获取。

2.2.1 xxx::class获取obj

// 和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)
}    

2.2.2 Class.forName().kotlin获取obj

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))
}

2.3 通过obj反射获取对象的成员、函数等

当使用反射访问的成员和函数为私有时,与java相同需要设置 isAccessabletrue 取消权限控制检查。

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获取到 KPropertyKMutableProperty,函数则会通过 KFunction,然后调用 call() 实现反射。

3 kotlin反射中基本结构的属性

3.1 kotlin的KClass

尽管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<KFunction<*>>
kClass.declaredMemberExtensionFunctions
// Collection<KProperty2<T, *, *>>
kClass.declaredMemberExtensionProperties
// Collection<KFunction<*>>
kClass.memberExtensionFunctions
// Collection<KProperty2<T, *, *>>
kClass.memberExtensionProperties
// KType
kClass.starProjectedType
属性或函数名称含义
isCompanion是否伴生对象
isData是否数据类
isSealed是否密封类
objectInstanceobject实例(如果是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<A>.)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()。这类方法其实有点”鸡肋“,更多时候我们希望获得的是此类的扩展方法。

3.2 kotlin的KCallable

kotlin把Class的属性(Property)、函数(Function)甚至构造函数都看作 KCallable,因为它们是可调用的,它们都是Class的成员。

在上面的例子中,获取kotlin的 KPropertyKMutablePropertyKFunction 分别使用了 KClass.declaredMemberPropertiesKClass.declaredFunctions 区分获取,既然kotlin将 KPropertyKFunction 都归类到 KCallable,同样也有一个函数可以同时获取到它们即 KClass.members,它的返回值是一个 Collection<KCallable<*>>,调用该函数将能获取到该类的成员和函数(不包含对象内的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<KParameter>此KCallable是否为抽象的
isFinal: Boolean此KCallable是否为final
isOpen: Boolean此KCallable是否为open
name: String此KCallable的名称
parameters: List<KParameter>调用KCallable需要的参数
returnType: KType此KCallable的返回类型
typeParameters: List<KTypeParameter>此KCallable的类型参数
visibility: KVisibility?此KCallable的可见性
call(vararg args: Any?): R给定参数调用此KCallable

3.3 获取参数信息(KParameter、KType、KTypeParameter)

kotlin把参数 分为3个类别,分别是函数的参数 KParameter、函数的返回值 KType 和类型参数 KTypeParameter

3.3.1 KParameter

使用 KCallable.parameters 即可获取一个 List<Parameter>,它代表的是函数(包括扩展函数)的参数。

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

通过上面的运行结果发现,对于属性和无参数的函数(如 instanceFieldinvokeMethod()),它们都有一个隐藏的参数为类的实例,而对于声明参数的函数(如 invokeMethodWithParam()),类的实例作为第1个参数,而声明的参数作为后续的参数。

3.3.2 KType

每一个 KCallable 都可以使用 returnType 获取返回值类型,它的结果类型是一个 KType

API描述
arguments: List<KTypeProjection>该类型的类型参数
classifier: KClassifier?该类型在类声明层面的类型,如该类型为List<String>,那么通过classifier得到结果为List(忽略类型参数)
isMarkedNullable: Boolean该类型是否标记为可空类型

我们在 Person 添加一个返回值为 List<String> 的方法演示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其实就是获取该参数在类层面对应的类型,如 Intclass kotlin.IntList<String>class kotlin.collections.List

3.3.3 KTypeParameter

KClassKCallable 中我们可以通过 typeParameters 获取类型参数,它返回的结果是 List<KTypeParameter>,不存在类型参数时就返回一个空的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]
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值