Kotlin学习笔记(六)-反射

[toc]

前言

这一节为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")

关于注解这里需要说下:

  1. getAnnotation() 通常获取注解的方法 返回一个注解
  2. getAnnotationsByType() 访问相同注解重复使用
  3. 当注解被@Retention(AnnotationRetention.BINARY)修饰时无法获取到注解
  4. 属性 SOURCE BINARY RUNTIME 只有当RUNTIME可以获取到注解(不设置默认是RUNTIME)
  5. AnnotationTarget 默认是class(当不设置的时候) 如果注解参数用 FIELD 其他参考AnnotationTarget定义
  6. 注解分作用类型

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")
    }
}
  1. 获取Class
    val Kclazz1 = People::class//类获取
    val Kclazz2 = people::class//对象获取
    val Kclazz3 = people.javaClass.kotlin//对象获取
  1. 反射创建对象
    这部分先要连接什么是主构造器(primaryConstructor):在类名后面写的构造器时主构造器,在类里面写的构造器时非主构造器。
    val primaryConstructor = Kclazz2.primaryConstructor!!
    val Kpeople1 = primaryConstructor.call("k-mark-1", 18)
    println("Kpeople1: $Kpeople1")
  1. 无主构造器
   /**
     * 无(主构造器)
     */
    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)
  1. 访问类中属性
    Kotlin只能获取全部属性集合 不支持获取某个指定的属性
  Kclazz2.memberProperties.forEach(::println)
  1. 修改属性的值
    只有属性被定义成 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")
  1. 反射方法
   Kclazz3.memberFunctions.forEach {
        println("function: $it")
    }

    Kclazz3.memberFunctions.forEach {
        println("name: ${it.name}")
        if (it.name == "hello2") {
            it.call(people, "你好!!!")
        }
    } 
  1. 访问扩展方法
    Kotlin只能访问对象中的扩展方法 而不能访问类文件的扩展方法
    Kclazz3.memberExtensionFunctions.forEach(::println)

    Kclazz3.memberExtensionFunctions.forEach {
        println(it.name)
    }
  1. 包级函数
    无法反射包级函数 因为本身包级函数就是为了能在任意处都能调用到的
  2. 反射注解
    Kclazz3.annotations.forEach(::println)//访问类的注解
    Kclazz3.memberProperties.first {
        it.name == "age"
    }.annotations.forEach {
        println("annotations: $it")
    }
  1. 成员
Kclazz3.members.forEach(::println)

反射的缺点

  1. 反射库大小的2.5M 编译ReleaseApk+混淆后会多出400多kb
  2. 接口比Java稍待完善
  3. 比java反射速度慢一些

结语

关于Kotlin反射有些地方比较难理解。因为是笔记很多我也是不是很清晰,反射就算是放在Java也属于高级语法了。慢慢磨练熟悉,多用多看,相信自己。鼓励自己。加油

Github源码直接运行,包含全部详细笔记

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容