Kotlin学习笔记(六)-反射
- 2019 年 12 月 20 日
- 笔记
前言
这一节为Kotlin反射,主要是在Kotlin中时用Java-Api来实现反射,使用Kotlin本身支持的反射API进行反射。还有2者的对比。要是对Java的反射不是很熟悉,可以花几分钟的时间先去网上找些Java反射的文章。关于Java的反射并不是这节的主要内,同时反射中也涉及到泛型的知识。其实有很多反射的地方关于泛型我也不敢说完全明白,也在代码中加了很多TODO,希望以后慢慢能熟能生巧,慢慢理解。
在Kotlin中调用JavaApi实现反射
1. 获取Class类
val clazz1: KProperty0<Class<Person>> = person::javaClass val clazz3: Class<out Person> = clazz1.get() val clazz2: Class<out Person> = person::class.java
2. 在Kotlin中使用Java反射API
因为是调用Java的API,那么这里只写一些简单的调用,具体Java反射原理和常用API可以找些文章,网上很多
@Poko data class Person(val name: String, val age: Int)
- 创建对象
//这里使用的无参数构造方法,之前说过data class是没有无参数的构造方法的 //需要用noArg库 val person2 = clazz1.get().newInstance() val person3 = clazz2.getConstructor(String::class.java, Int::class.java).newInstance("pack", 15)
- 获取属性
// val name1=clazz2.getDeclaredField("name").get(person3)//直接调用会报错 因为name是private的 无法访问 需要加上isAccessible=true val name2 = clazz2.getDeclaredField("name").apply { isAccessible = true }.get(person3)
- 获取方法 之前讲解data class时说过,编译器在运行时生成的.class文件会帮我们自动生成一些方法,其中就有copy方法
val person4 = clazz2.getDeclaredMethod("copy", String::class.java, Int::class.java).invoke(person3, "pack_copy", 18)
- 修改属性
clazz2.getDeclaredField("name").apply { isAccessible = true }.set(person3, "rose")//修改参数
在Java和Kotlin中,final的属性也是可以被修改的
3. 在Kotlin中利用Java反射获取Kotlin类中的成员
被反射的Kotlin类
@Poko class AnnotationTest1(val name: String = "name1") @Poko @Poko2 class AnnotationTest2(val name: String = "name2") class AnnotationTest3(@Poko val name: String = "name3") class AnnotationTest4(@Poko3 @Poko4 val name: String = "name4") fun Person.sayHello() { println(this.name) } fun sayHello() { println("say Hello World !!!") }
首先要明白一点Kotlin(如Main.kt)类,在编译后会生成MainKt类,但是这个类在Kotlin中时找不到的(在Java中可以找到),所以我们想在Kotlin中获取Kt类,可以使用方法Class.forName("")。
- 扩展方法
//扩展方法 Class.forName("com.qinglianyun.kotlin.class8.Main8_2Kt") .getDeclaredMethod("sayHello", Person::class.java) .invoke(null, Person("kotlin hello", 3))
- 包级方法
//包级方法 Class.forName("com.qinglianyun.kotlin.class8.Main8_2Kt") .getDeclaredMethod("sayHello") .invoke(null)
- 获取注解
val poko = Class.forName("com.qinglianyun.kotlin.class8.AnnotationTest1") .getAnnotation(Poko::class.java) Class.forName("com.qinglianyun.kotlin.class8.AnnotationTest3") .getDeclaredField("name").annotations.forEach { println("AnnotationTest3: $it")//什么都打印不出来 因为注解分注解类型 AnnotationTarget } //获取属性注解 Class.forName("com.qinglianyun.kotlin.class8.AnnotationTest4") .getDeclaredField("name").annotations.forEach { println("AnnotationTest4: $it")
关于注解这里需要说下:
- getAnnotation() 通常获取注解的方法 返回一个注解
- getAnnotationsByType() 访问相同注解重复使用
- 当注解被@Retention(AnnotationRetention.BINARY)修饰时无法获取到注解
- 属性 SOURCE BINARY RUNTIME 只有当RUNTIME可以获取到注解(不设置默认是RUNTIME)
- AnnotationTarget 默认是class(当不设置的时候) 如果注解参数用 FIELD 其他参考AnnotationTarget定义
- 注解分作用类型
1-6是注解相关知识,这部分参考Java注解,Java 注解(Annotation)
使用Kotlin反射库来实现反射
首先Kotlin的反射API是需要引入库的
//Kotlin 反射工具包 implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
被访问的数据类:
@Poko //生成无参数构造器 data class People(val name: String, @Poko5 var age: Int)//primaryConstructor { fun hello1() { println("hello1") } fun hello2(method: String) { println("hello2: $method") } fun String.times(other: Int) { println("类对象中的扩展方法sayHello2") } } fun People.sayHello1() { println("类文件的扩展方法sayHello1") } class NoPrimaryConstructor { constructor() { println("not primaryConstructor, no args") } constructor(num: Int) { println("not primaryConstructor, have args:$num") } }
- 获取Class
val Kclazz1 = People::class//类获取 val Kclazz2 = people::class//对象获取 val Kclazz3 = people.javaClass.kotlin//对象获取
- 反射创建对象 这部分先要连接什么是主构造器(primaryConstructor):在类名后面写的构造器时主构造器,在类里面写的构造器时非主构造器。
val primaryConstructor = Kclazz2.primaryConstructor!! val Kpeople1 = primaryConstructor.call("k-mark-1", 18) println("Kpeople1: $Kpeople1")
- 无主构造器
/** * 无(主构造器) */ val Kpeople2 = NoPrimaryConstructor::class.primaryConstructor?.call()//无住构造器 println("Kpeople2: $Kpeople2")//输出 null //正确访问 val call1 = NoPrimaryConstructor::class.constructors.first().call() val call = NoPrimaryConstructor::class.constructors.last().call(2)
- 访问类中属性 Kotlin只能获取全部属性集合 不支持获取某个指定的属性
Kclazz2.memberProperties.forEach(::println)
- 修改属性的值 只有属性被定义成 var才有效
Kclazz3.memberProperties.forEach(::println) (Kclazz3.memberProperties.first { it.name == "age" } as? KMutableProperty1<People, Int>)?.set(Kpeople1, 11) println("KMutableProperty1 Kclazz3: $Kpeople1") (Kclazz2.memberProperties.first { it.name == "age" }as? KMutableProperty1<People, Int>)?.set(Kpeople1, 13) println("KMutableProperty1 Kclazz2: $Kpeople1")
- 反射方法
Kclazz3.memberFunctions.forEach { println("function: $it") } Kclazz3.memberFunctions.forEach { println("name: ${it.name}") if (it.name == "hello2") { it.call(people, "你好!!!") } }
- 访问扩展方法 Kotlin只能访问对象中的扩展方法 而不能访问类文件的扩展方法
Kclazz3.memberExtensionFunctions.forEach(::println) Kclazz3.memberExtensionFunctions.forEach { println(it.name) }
- 包级函数 无法反射包级函数 因为本身包级函数就是为了能在任意处都能调用到的
- 反射注解
Kclazz3.annotations.forEach(::println)//访问类的注解 Kclazz3.memberProperties.first { it.name == "age" }.annotations.forEach { println("annotations: $it") }
- 成员
Kclazz3.members.forEach(::println)
反射的缺点
- 反射库大小的2.5M 编译ReleaseApk+混淆后会多出400多kb
- 接口比Java稍待完善
- 比java反射速度慢一些
结语
关于Kotlin反射有些地方比较难理解。因为是笔记很多我也是不是很清晰,反射就算是放在Java也属于高级语法了。慢慢磨练熟悉,多用多看,相信自己。鼓励自己。加油