Kotlin真香系列第七弹:反射

目录

写在前面

一、反射的基本概念

1.1、基本概念

1.2、反射的依赖

1.3、反射的常见用途

1.4、反射常用的数据结构

1.5、Kotlin中使用反射

二、反射实战——获取泛型实参

三、综合案例

3.1、为数据类实现DeepCopy

3.2、Model映射

3.3、可释放对象引用的不可空类型


写在前面

上一篇中咱们是说到了Kotlin中的泛型该如何使用——《Kotlin真香系列第六弹:泛型》,那今天继续来学习Kotlin语言,接着上一篇的脚步,一起来看看Kotlin中的反射的概念是什么以及如何应用。

一、反射的基本概念

1.1、基本概念

  • 反射是允许程序在运行时访问程序结构的一类特性
  • 程序结构包括:类、接口、方法、属性等语法特性

1.2、反射的依赖

Kotlin真香系列第七弹:反射_第1张图片

Java:JDK本身已经包含了Java的反射API
Kotlin:Kotlin的反射API是单独的一套,在使用的时候需要单独添加依赖
Kotlin真香系列第七弹:反射_第2张图片

1.3、反射的常见用途

  • 列出类型的所有属性、方法、内部类等等
  • 调用给定名称及签名的方法或访问指定名称的属性
  • 通过签名信息获取泛型实参的具体类型
  • 访问运行时注解及其信息完成注入或者配置操作

1.4、反射常用的数据结构

Kotlin真香系列第七弹:反射_第3张图片

反射数据结构对比:Kotlin vs Java

Kotlin真香系列第七弹:反射_第4张图片

KType对应于Java的Type,KClass对应于Java的Class。KProperty和Java中的Field本身是有差别的,Kotlin中的属性功能更强大,但是二者的作用比较类似,所以这里是个约等于的关系。Java里面没有函数的概念,只有方法的概念,相当于强制绑定了receiver,KFunction既可以作为顶级函数,也可以作为方法使用。

1.5、Kotlin中使用反射

在Kotlin中使用反射要注意一点,Kotlin跟Java是完全兼容的,所以Java的反射也是可以使用的:
Kotlin真香系列第七弹:反射_第5张图片

上面就是关于反射的基础理论知识,咱们了解了这些理论知识之后,下面就通过具体的案例来实际体验一下反射的具体用法。

二、反射实战——获取泛型实参

这一部分咱们来通过代码实际体验一下如何通过反射来获取泛型实参,主要是来熟悉一下KType与KClass的区别,同时也能加深对泛型擦除的理解,来看一下咱们需要实现的效果:

Kotlin真香系列第七弹:反射_第6张图片

通过反射的方式来获取泛型内部的UserDTO,也就是获取function返回值类型的泛型实参。

具体代码实现如下:

class UserDTO

//定义接口,内部定义一个getUsers()方法
interface Api {
    fun getUsers(): List
}
//Any添加类型转换的扩展方法
fun  Any.safeAs(): T? {
    return this as? T
}

//父类抽象类,this::class拿到的是子类的实例
abstract class SuperType {
    val typeParameter by lazy {
        //supertypes获取到的是List,然后调用first()获取到KType
        this::class.supertypes.first().arguments.first().type!!
    }

    //Java反射
    val typeParameterJava by lazy {
        this.javaClass.genericSuperclass?.safeAs()!!.actualTypeArguments.first()
    }
}

//子类
class SubType : SuperType()

fun main() {
    //declaredFunctions拿到函数,使用filter根据KFunction的name过滤,因为只有一个这里可以用first
    Api::class.declaredFunctions.first { it.name == "getUsers" } //KFunction
            //returnType拿到KType,KType可以拿到泛型实参
        .returnType.arguments.forEach {
        println(it)
    }

    //Api::getUsers拿到函数引用,直接就是KFunction
    Api::getUsers.returnType.arguments.forEach {
        println(it)
    }

    //使用Java反射Api,Api::class.java拿到Java的Class,然后getDeclaredMethod拿到Method
    Api::class.java.getDeclaredMethod("getUsers")
            //genericReturnType拿到Java的Type,然后转成子接口ParameterizedType
        .genericReturnType.safeAs()?.actualTypeArguments?.forEach {
        println(it)
    }

    val subType = SubType()
    subType.typeParameter.let(::println) //Kotlin
    subType.typeParameterJava.let(::println) //Java
}

结果如下:

Kotlin真香系列第七弹:反射_第7张图片

三、综合案例

3.1、为数据类实现DeepCopy

我们知道对于数据类啊,本身编译器是帮我们生成了一个copy()的方法,但是这个copy()实际上是一个浅拷贝,那我们现在想要实现一个深拷贝该怎么实现呢?所以这个案例咱们会一起来熟悉一下反射API包括类、构造器等的使用,熟悉数据类默认的复制实现机制,并且能够综合运用扩展方法、泛型、反射等知识点,最后实现深拷贝。

案例效果:

首先我们定义了两个数据类:

然后通过深拷贝实现如下效果:

Kotlin真香系列第七弹:反射_第8张图片

具体的代码如下:

fun  T.deepCopy(): T {
    //此功能的前提条件是作用于数据类,所以首先判断是否是数据类
    if (!this::class.isData) {
        return this
    }

    //对于数据类来说一定有primaryConstructor主构造器
    return this::class.primaryConstructor!!.let {
        //显示声明出来
            primaryConstructor ->
        //primaryConstructor.parameters拿到参数
        primaryConstructor.parameters.map { parameter ->
            //(this::class as KClass)强转为KClass,数据类不会被继承,不需要型变
            val value =
                (this::class as KClass).memberProperties.first { it.name == parameter.name }
                    .get(this) //value是this里面属性对应的值
            //classifier一般就是KClass,类型强转KClass,然后判断这个属性是否是数据类
            if ((parameter.type.classifier as? KClass<*>)?.isData == true) {
                parameter to value?.deepCopy() //做深拷贝,这里是一个递归调用
            } else { //基本类型直接赋值
                parameter to value
            }
            //到这里Map得到的就是Pair
        }.toMap() //把Pair的List转成Map
            .let(primaryConstructor::callBy) //callBy返回的就是我们想要的实例
    }
}

//定义数据类
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,
        "Jarchie",
        Group(
            0,
            "Kotliner.cn",
            "China"
        )
    )

    val copiedPerson = person.copy() //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(person)
    println(copiedPerson)
    println(deepCopiedPerson)
}

执行结果:

Kotlin真香系列第七弹:反射_第9张图片

3.2、Model映射

首先来看一下这个案例需要实现的效果:

Kotlin真香系列第七弹:反射_第10张图片
通过调用mapAs()方法就可以把一个对象转成另外一个类型,不用在将每个属性一一赋值,咱们都是要用反射来实现这个功能。

下面来看具体的代码实现:

//mapping fields. Using Annotations next chapter.
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
)

//通过任意类型映射成另外的任意类型
inline fun  From.mapAs(): To {
    //把A类型的From的成员转成map
    return From::class.memberProperties.map {
        //拿到name和value
        it.name to it.get(this)
    }.toMap().mapAs() //调用下面实现好的mapAs()方法
}

//通过Map直接映射到任意类型,使用Map中的key-value映射到类型对应的字段
inline fun  Map.mapAs(): To {
    //构造To的类型,此处是To的构造方法调用,和上一个案例一样使用它的主构造器
    return To::class.primaryConstructor!!.let {
        it.parameters.map {
                parameter ->
            //this[parameter.name]这是map的get调用,后面是判断是否可空类型,若为空并且接收空则返回null
            // 不接收空并且map中没有则抛出异常
            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类型
    }
}

fun main() {
    val userDTO = UserDTO(
        0,
        "Jarchie1",
        "https://profile.csdnimg.cn/4/F/4/1_jarchie520",
        "https://blog.csdn.net/jarchie520",
        "https://github.com/JArchie"
    )

    val userVO: UserVO = userDTO.mapAs()
    println(userVO)

    val userMap = mapOf(
        "id" to 0,
        "login" to "Jarchie2",
        "avatarUrl" to "https://profile.csdnimg.cn/4/F/4/1_jarchie520",
        "url" to "https://blog.csdn.net/jarchie520"
    )

    val userVOFromMap: UserVO = userMap.mapAs()
    println(userVOFromMap)
}

执行结果:

3.3、可释放对象引用的不可空类型

如下图所示,有一个引用bitmap,它引用了一个类型,这个类型持有了很多内存或者资源,这些资源我们希望在最后能够手动的释放掉,释放之后希望GC能够尽快的把它给回收掉:

Kotlin真香系列第七弹:反射_第11张图片

那怎么搞呢?我们要实现的效果就是如下图所示的这样,定义了一个属性代理,那么这个bitmap就是不可空的类型,此时释放的点其实是在属性代理的内部,那么它就是可空的,意味着它就可以被置为null,所以这种使用方式既可以作为不可空的类型去使用,同时也能够很快的把Bitmap对象释放掉:

Kotlin真香系列第七弹:反射_第12张图片

具体的代码实现如下:

//region impl
//直接new一个ReleasableNotNull
fun  releasableNotNull() = ReleasableNotNull()

//定义属性代理类,它有个泛型T,实现ReadWriteProperty接口方便生成set getValue
class ReleasableNotNull : ReadWriteProperty {
    //定义成员backingfield,最后释放的时候实际上是把value置为null了,属性使用的过程中一直是不可空的类型
    private var value: T? = null

    //value为空时抛出异常,未初始化或者已经被释放
    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

    //释放,将value置为null
    fun release() {
        value = null
    }
}

//直接仿写lateinit的isInitialized方法
inline val KProperty0<*>.isInitialized: Boolean
    get() {
        isAccessible = true //不加这行代码会抛异常,没有访问权限
        //返回delegate对象ReleasableNotNull的isInitialized()方法,并且类型强转
        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.")
}

//endregion

//region demo
class Bitmap(val width: Int, val height: Int)

class Activity {
    private var bitmap by releasableNotNull()

    fun onCreate() {
        //属性引用调用isInitialized判断是否初始化
        //this::bitmap默认绑定了receiver,所以上面是KProperty0
        println(this::bitmap.isInitialized)
        bitmap = Bitmap(1920, 1080)
        println(::bitmap.isInitialized)
    }

    fun onDestroy() {
        println(::bitmap.isInitialized)
        ::bitmap.release()
        println(::bitmap.isInitialized)
    }
}

fun main() {
    //模拟Activity进行测试,模拟bitmap初始化的流程
    val activity = Activity()
    activity.onCreate()
    activity.onDestroy()
}
//endregion

执行结果:

Kotlin真香系列第七弹:反射_第13张图片

好了,关于反射的东西就先到这里吧,这几个案例后面可以多看看,了解一下具体的用法!

今天的内容就这么多了,咱们下期再会!

祝:工作顺利!

你可能感兴趣的:(Kotlin)