反射
在程序的运行过程中动态地调用类的属性和方法:对于任意一个类,都能够知道这个类的所有属性和方法,都能够调用他的任意一个属性和方法,这种动态地获取信息以及动态调用对象的方法的功能。
我们通过编译工具码代码写的文件 -> .kt/.java
通过类加载器classLoader将上述文件加载到内存中
然后进一步加载为可执行的文件 -> 反射就作用于这一区域,也就是字节码阶段。
//1、只知道类名
val clz = Person::class
//2、已经知道某个对象
val xw = Person()
val clz2 = xw.javaClass.kotlin
//可以通过class对象获取类的详细信息
println(clz2.simpleName)
反射中常用的属性、方法
属性/方法 | 作用 |
---|---|
primaryConstructor | 获取主构造函数 |
constructors | 获取所有构造函数 |
memberFunctions | 获取自己和父类的所有方法 |
declaredFunctions | 获取自己声明的方法 |
functions | 获取自己的所有方法(包括静态方法)和父类的所有非静态方法 |
findAnnotation | 检索某个注解 |
annotation | 访问注解 |
反射的实战理解
我们会有这样一种需求:我们有一个函数,需要获取一个对象,但是并不知道这个对象的具体类型,这个时候
- 使用默认的构造函数创建
//创建对象
fun createObj(clz:KClass<out Any>):Any{
//1、使用默认的构造函数创建
//必须提供无参的构造函数
return clz.createInstance()
}
- 当构造函数有参数时
fun main() {
val lw = createObj(Student2::class) as Student2
println(lw.name)
}
//创建对象
fun createObj(clz:KClass<out Any>):Any{
//2、有参的构造函数
//创建对象 是通过构造函数来创建的
//找到有参的构造函数 -> 创建对象
val priCons = clz.primaryConstructor
//调用构造函数
val obj = priCons!!.call("老王")
return obj
}
class Student2(var name:String){
constructor():this("小王"){}
}
- 通过类名和函数名调用方法
//调用函数
fun invokeFun(clz:KClass<out Any>,funName:String){
//获取默认的主构造函数
val priCons = clz.primaryConstructor
//创建对象
val obj = priCons?.call("老王")
//查找这个函数是否存在
clz.functions.find { it.name==funName }.also {
//当调用类里面的方法时 必须将对象传递过去
it!!.call(obj,"西大")
}
}
- 通过Kclass和属性名访问属性
class Student2(var name:String){
constructor():this("小王"){}
var age:Int = 20
fun show(des:String){
println("my name is $name des:$des")
}
}
//调用属性 通过反射可以调用所有属性(不管是私有还是公开) 非常暴力
fun invokeProperty(clz:KClass<out Any>,propName:String){
//创建对象
val primary = clz.primaryConstructor
val obj = primary!!.call("Android大神")
//查找age属性
clz.memberProperties.find { it.name==propName }.also {
//获取对象的get方法 call其实就是调用对象的get方法
val value = it?.call(obj)
println("获取属性${propName}的值:$value")
}
}
class Student2(var name:String){
constructor():this("小王"){}
var age:Int = 20
fun show(des:String){
println("my name is $name des:$des")
}
}
//调用属性 通过反射可以调用所有属性(不管是私有还是公开) 非常暴力
fun invokeProperty(clz:KClass<out Any>,propName:String){
//创建对象
val primary = clz.primaryConstructor
val obj = primary!!.call("Android大神")
//查找age属性
clz.memberProperties.find { it.name==propName }.also {
//获取对象的get方法 call其实就是调用对象的get方法
// val value = it?.call(obj)
// println("获取属性${propName}的值:$value")
//调用对象的set方法
//将KProperty类型转化为KMutableProperty类型
//KMutableProperty1表示函数只有一个参数
//KMutableProperty1<T,R>
if (it != null){
//如果属性的修饰符为private 必须设置是否可以设置
it?.isAccessible = true
val mPro = it as KMutableProperty1<Any, Any>
mPro.set(obj,10)
val result = it.call(obj)
println(result)
}
}
}
注解
什么是注解?
注解其实就是某个类或属性……身份的标识,本身没有任何作用,注解往往和反射联合使用。比如,在通过反射创建一个对象时,一般需要判断这个类有没有某个注解,有的话就能返回实例化的对象,否则无法创建对象。
注解的种类
- @Target:指定这个注解可用于哪些元素(类、函数、属性、表达式,等等)。
- @Retention:指定这个注解的信息是否被保存到编译后的class文件中,以及在运行时是否可以通过反射访问它。
- @Repeatable:允许在单个元素上多次使用同一个注解。
- @MustBeDocumented:表示这个注解是公开API的一部分,在自动产生的API文档的类或者函数签名中,应该包含这个注解的信息。
@Target(AnnotationTarget.CLASS)
annotation class TableName
@Target(AnnotationTarget.CONSTRUCTOR)
annotation class MYCons
@Target(AnnotationTarget.PROPERTY)
annotation class MYParam
@Target(AnnotationTarget.FUNCTION)
annotation class MYFunc
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class MyFuncParam
@TableName
class Person @MYCons constructor(){
@MYParam val name:String = "jack"
@MYCons constructor(name:String):this(){
}
@MYFunc fun show(@MyFuncParam des:String){
}
}
注解的生命周期
SOURCE:处于代码编写时期
BINARY:持续到字节码文件时期
RUNTIME:整个运行阶段都存在
注:注解类中定义参数必须使用val,而不能使用var。
实战
模拟Android中的Room创建表的实现原理
读取Table(表)的信息,并创建对应的实例对象
/**
*@Description
*@Author PC
*@QQ 1578684787
*/
class Student
//模型数据
@Entity
data class User(
@ColumnInfo var id:Int,
@ColumnInfo var name:String,
@ColumnInfo var icon:String
){
override fun toString(): String {
return "User(id=$id,name='$name',icon='$icon')"
}
}
//表明 -> 类名 一一对应
//类注解
//Retention默认值为AnnotationRetention.RUNTIME
@Retention
@Target(AnnotationTarget.CLASS)
annotation class Entity
//属性注解
@Retention
@Target(AnnotationTarget.PROPERTY)
annotation class ColumnInfo
//模拟数据的查询
fun selectData():Map<String,Map<String,Any>>{
//模拟有两个表User Student
//使用Map封装数据
val userData = mapOf(
Pair("id",1),
Pair("name","joke"),
Pair("icon","www.google.com")
)
val studentData = mapOf(
Pair("Sid",1),
Pair("name","李王开"),
Pair("address","西南大学")
)
return mapOf(
Pair("User",userData),
Pair("Student",studentData)
)
}
fun autoParseFromTable(modelClz: KClass<out Any>):Any?{
//先从数据库中读取出表的对应数据
val datas = selectData()
//判断传递过来的KClass对象有没有Entity注解
val entity = modelClz.findAnnotation<Entity>()
return if (entity==null){
//传递过来的类没有Entity注解
//不能进行转换
null
}else{
//如果传递过来的类有Entity注解
//就创建对象实例
//获取表名
val tableName = modelClz.simpleName
val info = datas[tableName]
//创建对象 将info的数据 填充到对象对应的属性中
//使用默认的主构造函数创建
val constructor = modelClz.primaryConstructor
//创建一个数组保存解析的属性值
//创建的数组元素个数 和 构造函数中参数的个数相同 初始值为null
val params = arrayOfNulls<Any>(constructor!!.parameters.size)
//遍历构造函数的参数
constructor.parameters.forEach{
//从数据源中获取这个参数对应的值
val value = info?.get(it.name)
//将这个值保存到数组中
params[it.index] = value
}
//调用构造函数创建对象
//将一个数组传递给可变参数时 需在参数名前加*
//vararg 对应的是Array类型的数组 不能是List
constructor?.call(*params)
}
}
fun main(){
val user = autoParseFromTable(User::class) as User
println(user.id)
}