Kotlin学习手记——反射
Posted 川峰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin学习手记——反射相关的知识,希望对你有一定的参考价值。
基本上和java是一一对应的,其中KType表示的是未擦除的泛型,KClass获取的是实际运行时的类型,不带泛型参数的。
Kotlin使用反射的话,唯一一点不好的就是需要引入一个体积非常大的库:
dependencies
// kotlin反射库,大小2.5M左右
implementation "org.jetbrains.kotlin:kotlin-reflect:1.4.20"
没错,有2.5M,不过编译后的大小还能接受
kotlin获取KClass通过两个冒号
var cls: KClass<String> = String::class
cls.java // 转成java的Class<String>
cls.java.kotlin // 再转回到kotlin的KClass
// 获取定义在类中的属性(直接写在类当中的)
val property = cls.declaredMemberProperties.firstOrNull()
KClass是不带泛型的类,typeOf能拿到具体的泛型实际类型:
val mapCls = Map::class
println(mapCls) // 输出class kotlin.collections.Map
val mapType = typeOf<Map<String, Int>>() // 拿到 KType
mapType.arguments.forEach // 拿到 KType 中的每个参数泛型的类型
println(it) // 输出 kotlin.String 和 kotlin.Int
拿到KClass之后可以通过KClass的方法获取各种其他属性了
一个简单的示例:
open class S(val sex : Boolean)
fun superFun()
class A(val name : String, sex: Boolean) : S(sex)
fun String.hello()
fun foo()
var age : Int = 0;
fun A.test()
上面的class A继承了一个类,并且类的内部有其他类定义的扩展方法,本身也定义了一个扩展方法。
KClass提供了很多方法获取类的属性和方法,但是有一些区别,方法比较多,可以看一下区别:
fun main()
// 能得到:age, name, foo(), (String.)hello(), equals(), hashCode(), toString(), sex, superFun()
println(A::class.members) 获取所有的成员属性和方法,包括其他类的扩展方法,包括父类的方法,但不包括构造方法
// 能得到:foo(), (String.)hello(), equals(), hashCode(), toString(), superFun()
println(A::class.functions) 获取所有的方法, 包括扩展方法,包括父类的
// 能得到:age, name, sex
println(A::class.memberProperties) 获取所有的成员属性 非扩展, 包括父类
// 能得到:foo(), equals(), hashCode(), toString(), superFun()
println(A::class.memberFunctions) 获取所有的成员方法 非扩展, 包括父类
// 能得到:(String.)hello()
println(A::class.memberExtensionFunctions) 获取所有的扩展方法, 包括父类
// 能得到:[]
println(A::class.memberExtensionProperties) 获取所有的扩展属性, 包括父类
// 能得到:age, name
println(A::class.declaredMemberProperties) 获取到所有定义的属性 当前类
// 能得到:foo()
println(A::class.declaredMemberFunctions) 获取到所有定义的方法(普通方法,非扩展方法,非静态方法)当前类
// 能得到:age, name, foo(), (String.)hello()
println(A::class.declaredMembers) 获取到所有定义的成员包括属性和方法(普通方法和扩展方法)当前类
// 能得到:foo(), (String.)hello()
println(A::class.declaredFunctions) 获取到所有定义的方法(普通方法和扩展方法) 如果是java类可以获取父类的方法
// 能得到:(String.)hello()
println(A::class.declaredMemberExtensionFunctions) 获取定义在当前类的扩展方法
// 能得到:[]
println(A::class.declaredMemberExtensionProperties) 获取定义在当前类的扩展属性
可以看出以declared开头的方法基本上只能获取当前类的属性和方法,不带declared开头的方法则同时可以获取到父类的相关属性和方法。
还有一点需要注意的是,这里kotlin里面所指的扩展属性和扩展方法一般是指直接写在当前类中的其他类的扩展方法,如上面的A里面的String.hello()方法。如果是A类在某个地方定义的扩展方法是获取不到的,如上面的A.test()方法。这点跟java有点不一样。
nestedClasses
获取内部类
B::class.nestedClasses//获取内部类
objectInstance
获取object单例的实例,如果不是单例类则返回可能为null
B::class.objectInstance?.hello() //获取object实例
A::class.objectInstance?.foo() //如果类不是一个object, 则返回null
类内部的其他类如何获取外部类的实例对象:
class A
fun String.hello()
this 表示当前String对象
this@A 表示外部当前的class A对象
java也是一样,内部类获取外部类的实例时需要通过,A.this
获取
获取泛型实参:
1.获取接口某个方法的返回值类型的泛型参数
interface Api
fun getUsers(): List<UserDTO>
获取上面 Api 接口的 getUsers() 返回类型的泛型参数类 UserDTO
有几种方式,第一种是根据name来比较判断找到对应的方法:
//获取到 Api的getUsers() 方法 通过 filter
val functions = Api::class.declaredFunctions.filter it.name == "getUsers"
val getUsers : KFunction<*> = functions.get(0)
getUsers.returnType.arguments.forEach
println("getUser的返回值泛型:$it")
//获取函数的返回值参数的泛型类型UserDTO 通过 first
Api::class.declaredFunctions.first it.name == "getUsers"
.returnType.arguments.forEach
println("getUser的返回值泛型:$it")
还可以直接通过函数引用获取 Api::getUsers
得到的就是一个KFunction
Api::getUsers.returnType.arguments.forEach
println("getUser的返回值泛型2:$it")
显然这种方式最简单了。
还可以通过java的反射方式来获取:
//Api::class.java是获取到对应java的class Class<Api> 然后可以调用java的反射方法获取泛型类型
Api::class.java.getDeclaredMethod("getUsers")
.genericReturnType.safeAs<ParameterizedType>()?.actualTypeArguments?.forEach
println(it)
//safeAs是定义的一个Any的扩展方法
fun <T> Any.safeAs(): T?
return this as? T
//safeAs扩展方法可以简写下面的代码,等价于上面的代码
(Api::class.java.getDeclaredMethod("getUsers")
.genericReturnType as ParameterizedType).actualTypeArguments?.forEach
println(it)
只能说java的方式也可以,但是这种也太麻烦了。。还是全部用kotlin的方法吧,不然得各种强转各种判空?.
2.获取接口类的泛型
abstract class SuperType<T>
//kotlin反射方法获取
val typeParameter by lazy
//this是实际运行子类型, supertypes拿到父类型,first是第一个父类型即SuperType,arguments获取到泛型参数列表,只有一个可以first(),
// first()方法返回的是KTypeProjection,KTypeProjection.type才返回KType
this::class.supertypes.first().arguments.first().type!!
//java反射方法获取
val typeParameterJava by lazy
this.javaClass.genericSuperclass.safeAs<ParameterizedType>()!!.actualTypeArguments.first()
open class SubType : SuperType<String>()
获取上面 SubType类实现的SuperType接口类的泛型:
val subType = SubType()
subType.typeParameter.let(::println) // kotlin.String
subType.typeParameterJava.let(::println) // class java.lang.String java获取的永远是java类型的描述
关键代码就是这句:this::class.supertypes.first().arguments.first().type
这里的话主要注意这个this运行时是实际的子类型(OO多态),所以最后是可以直接强转的。
上面代码是只有一个父类,如果有多个父类,会有问题,需要修改一下:
abstract class SuperType<T>
val typeParameter2 by lazy
//实际中,如果子类是open可继承的可能还会有子类,具体要看使用的类
//此时需要找到合适的父类再操作,可以根据名字去比较,这里示例直接判断不是空的
this::class.allSupertypes.first it.arguments.isNotEmpty() .arguments.first().type!!
//等价上面
//this::class.allSupertypes.filter it.arguments.isNotEmpty().first().arguments.first().type!!
open class SubType : SuperType<String>()
class SubType2: SubType()
获取上面 SubType2类的父类实现的SuperType接口类的泛型:
val subType2 = SubType2()
subType2.typeParameter2.let(::println) // kotlin.String
实例:为数据类实现 DeepCopy
fun <T : Any> T.deepCopy(): T
//是数据类data class 才拷贝
if(!this::class.isData)
return this
//primaryConstructor获取主构造器,因为执行到这里的是数据类肯定有主构造器,所以!!强转,不用判空
return this::class.primaryConstructor!!.let
primaryConstructor ->
primaryConstructor.parameters.map parameter ->
//(this::class as KClass<T>)逆变转协变
val value = (this::class as KClass<T>).memberProperties.first it.name == parameter.name //成员属性名和构造函数的参数名相等
.get(this)
//classifier先转成KClass,然后判断是否是数据类
if((parameter.type.classifier as? KClass<*>)?.isData == true)
parameter to value?.deepCopy() //如果value是数据类继续调用value的deepCopy()方法深拷贝 递归
else
parameter to value // 如果value不是数据类直接返回,(K to V)是返回一个Pair对象
.toMap() //Pair集合转Map集合
.let(primaryConstructor::callBy) //callBy调用构造函数构造对象, callBy需要一个Map<KParameter, Any?>参数就是当前的map对象
调用测试代码:
data class Person(val id: Int, val name: String, val group: Group)
data class Group(val id: Int, val name: String, val location: String)
fun main()
val person = Person(
0,
"hello",
Group(
0,
"Kotliner.cn",
"China"
)
)
val copiedPerson = person.copy()
val deepCopiedPerson = person.deepCopy()
println(person === copiedPerson) //false
println(person === deepCopiedPerson) //false
println(person.group === copiedPerson.group) //true for shallow copy.
println(person.group === deepCopiedPerson.group) //false
println(deepCopiedPerson)
上面的例子中主要有几点需要注意的:
this::class.isData
判断是否是数据类 data classthis::class.primaryConstructor
获取主构造器,因为是数据类一定有主构造器,所以可以强转!!
primaryConstructor.parameters
let里面调用当前primaryConstructor对象的parameters获取所有的构造器参数this::class as KClass<T>
逆变转协变,否则this.class返回一个协变点out T, 而get()方法接受一个逆变点,会报错memberProperties.first it.name == parameter.name
数据类的特点是构造器的参数名和成员的属性名相等parameter.type.classifier as? KClass<*>
type参数需要调用classifier先转成KClass然后再判断是否是数据类parameter to value?.deepCopy()
如果value是数据类继续调用value的deepCopy()方法深拷贝,这里是一个递归调用,K to V 是返回的一个 Pair对象.toMap()
将Pair集合转Map集合.let(primaryConstructor::callBy)
调用主构造器,callsBy是KCallable接口的方法,KFunction是KCallable的子类,因此所有的KFunction都可以调用callBy,callBy接受一个map参数正好就是let前面返回的结果。
这个例子有一个完整的开源库代码 KotlinDeepCopy 是由我参阅的学习资料的大神Bennyhuo所写的,但是貌似目前没什么issue, 慎用,可以当案例学习一下。
实例:实现 Model 映射
这个例子是实现一个拷贝工作,将一个对象里的字段赋值给另一个对象里面的同名字段,跟深拷贝的例子有点相似
//任意对象转其他对象(成员属性名相同)
inline fun <reified From : Any, reified To : Any> From.mapAs(): To
//所有成员转成Map集合再调用下面的Map转对象方法即可
return From::class.memberProperties.map it.name to it.get(this)
.toMap().mapAs()
//Map转对象(成员属性名相同) 默认只处理数据类
inline fun <reified To : Any> Map<String, Any?>.mapAs(): To
//primaryConstructor反射调用主构造器
return To::class.primaryConstructor!!.let
it.parameters.map
parameter ->
// this[parameter.name] 有可能为null, 如果目标对象To的构造器参数类型可以接受null类型就直接返回一个null 否则抛异常
parameter to (this[parameter.name] ?: if(parameter.type.isMarkedNullable) null
else throw IllegalArgumentException("$parameter.name is required but missing."))
.toMap()
.let(it::callBy)//callBy调用主构造器构造出一个To类型的对象
第一个方法的实现实际上是调用第二个方法的,所以只需实现第二个方法即可,这里依然是先获取主构造器To::class.primaryConstructor
,获取了主构造器之后拿到它的参数列表进行map操作,map里面依然是返回当前参数 parameter to value
,to 操作符左边的是To对象的也就是目标对象, to 操作符右边的是当前调用.mapAs的map对象,因此通过this[parameter.name]访问它里面的同名参数的value值,但是这个值可能为null, 不为null就返回 ?: 左边它自身,为null还需一个处理就是如果To类型即目标类的构造函数的这个当前参数可接受可空类型 ,就直接传null, 否则抛异常。
调用测试代码:
data class UserVO(val login: String, val avatarUrl: String)
data class UserDTO(
var id: Int,
var login: String,
var avatarUrl: String,
var url: String,
var htmlUrl: String
)
fun main()
val userDTO = UserDTO(
0,
"world",
"https://ccccccc",
"https://ddddddddd",
"https://eeeeeeeeeee"
)
val userVO: UserVO = userDTO.mapAs()
println(userVO)
val userMap = mapOf(
"id" to 0,
"login" to "hello",
"avatarUrl" to "https://aaaaaaa",
"url" to "https://bbbbbbbb"
)
val userVOFromMap: UserVO = userMap.mapAs()
println(userVOFromMap)
实例:可释放对象引用的不可空类型
这个例子主要是模仿了一个android当中释放bitmap对象赋值为null的场景,在kotlin当中如果你定义了一个 var bitmap: Bitmap, 然后在onDestroy方法里面将其置为null, 但是这样写bitmap=null是不行的,因为定义的时候是一个不可空类型,这就矛盾了。
fun <T : Any> releasableNotNull() = ReleasableNotNull<T>()
class ReleasableNotNull<T: Any>: ReadWriteProperty<Any, T>
private var value: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T
return value ?: throw IllegalStateException("Not initialized or released already.")
override fun setValue(thisRef: Any, property: KProperty<*>, value: T)
this.value = value
fun isInitialized() = value != null
fun release()
value = null
inline val KProperty0<*>.isInitialized: Boolean
get()
//允许反射获取
isAccessible = true
//this.getDelegate()获取属性代理的实例
return (this.getDelegate() as? ReleasableNotNull<*>)?.isInitialized()
?: throw IllegalAccessException("Delegate is not an instance of ReleasableNotNull or is null.")
fun KProperty0<*>.release()
isAccessible = true
(this.getDelegate() as? ReleasableNotNull<*>)?.release()
?: throw IllegalAccessException("Delegate is not an instance of ReleasableNotNull or is null.")
class Bitmap(val width: Int, val height: Int)
class Activity
private var bitmap by releasableNotNull<Bitmap>()
fun onCreate()
//::bitmap省略了this, 默认绑定了当前对象为receiver
println(this::bitmap.isInitialized)
println(this::bitmap.isInitialized)
bitmap = Bitmap(1920, 1080)
println(::bitmap.isInitialized)
fun onDestroy()
println(::bitmap.isInitialized)
::bitmap.release()
println(::bitmap.isInitialized)
fun main()
val activity = Activity()
activity.onCreate()
activity.onDestroy()
这个例子中主要利用了属性代理,然后有两个比较特殊的定义分别实现了属性代理接口KProperty0
的扩展属性KProperty0<*>.isInitialized
和扩展方法KProperty0<*>.release()
。
KProperty0
表示没有receiver(其实是绑定当前调用对象this作为receiver了)KProperty1
表示有1个receiver, KProperty2
表示有2个receiver。
isAccessible = true
允许反射操作,跟java一样也要设置一个accessible为true。
this.getDelegate()
获取的是当前属性代理接口的实际代理对象,而 this.getDelegate() as? ReleasableNotNull<*>
这个是转换成实际类型,然后调用实际类型的相关属性或方法即可,当然这个对象可能为null或者不是一个ReleasableNotNull类型的,这时需要抛异常。
以上是关于Kotlin学习手记——反射的主要内容,如果未能解决你的问题,请参考以下文章