Kotlin入门系列:第八章 反射

文章目录

  • 1 kotlin和java反射基本结构对比
  • 2 kotlin反射的使用
    • 2.1 获取KClass
    • 2.2 通过KClass获取obj
      • 2.2.1 xxx::class获取obj
      • 2.2.2 Class.forName().kotlin获取obj
    • 2.3 通过obj反射获取对象的成员、函数等
  • 3 kotlin反射中基本结构的属性
    • 3.1 kotlin的KClass
    • 3.2 kotlin的KCallable
    • 3.3 获取参数信息(KParameter、KType、KTypeParameter)
      • 3.3.1 KParameter
      • 3.3.2 KType
      • 3.3.3 KTypeParameter

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

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

Kotlin入门系列:第八章 反射_第1张图片

  • 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>
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()。这类方法其实有点”鸡肋“,更多时候我们希望获得的是此类的扩展方法。

3.2 kotlin的KCallable

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

在上面的例子中,获取kotlin的 KPropertyKMutablePropertyKFunction 分别使用了 KClass.declaredMemberPropertiesKClass.declaredFunctions 区分获取,既然kotlin将 KPropertyKFunction 都归类到 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

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

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

3.3.1 KParameter

使用 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

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

3.3.2 KType

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

API 描述
arguments: List 该类型的类型参数
classifier: KClassifier? 该类型在类声明层面的类型,如该类型为List,那么通过classifier得到结果为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其实就是获取该参数在类层面对应的类型,如 Intclass kotlin.IntListclass kotlin.collections.List

3.3.3 KTypeParameter

KClassKCallable 中我们可以通过 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]

你可能感兴趣的:(Kotlin)