User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Kotlin   ›   Kotlin Notes   ›   正文
Kotlin Notes

Kotlin-反射—反射(二十九)

2021-02-01 19:55:05
1953  0 0
参考目录 隐藏
1) 反射
2) 反射运用
3) 例:获取泛型实参
4) 1、获取接口某个方法的返回值类型的泛型参数
5) 2、获取接口类的泛型
6) 例:为数据类实现 DeepCopy
7) 例:实现 Model 映射
8) 例:可释放对象引用的不可空类型

阅读完需:约 15 分钟

反射

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Kotlin中使用反射功能所需的运行时组件作为单独的 JAR 文件(kotlin-reflect.jar)分发。这样做是为了减少不使用反射功能的应用程序所需的运行时库的大小。

dependencies {
    // kotlin反射库,大小2.5M左右
    implementation "org.jetbrains.kotlin:kotlin-reflect:1.4.20"
}




基本上和java是一一对应的,其中KType表示的是未擦除的泛型,KClass获取的是实际运行时的类型,不带泛型参数的。


反射运用

最基本的反射功能是获取 Kotlin 类的运行时引用。

kotlin获取KClass通过两个冒号

val c = MyClass::class
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 获取


例:获取泛型实参

为了更好的理解 KType 与 KClass

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

完整例子:

interface Api {
    fun getUsers(): List<UserDTO>
}

abstract class SuperType<T> {
    val typeParameter by lazy {
        this::class.supertypes.first().arguments.first().type!!
    }

    val typeParameterJava by lazy {
        this.javaClass.genericSuperclass.safeAs<ParameterizedType>()!!.actualTypeArguments.first()
    }
}

class SubType : SuperType<String>()

fun main() {
    Api::class.declaredFunctions.first { it.name == "getUsers" }
        .returnType.arguments.forEach {
        println(it)
    }

    Api::getUsers.returnType.arguments.forEach {
        println(it)
    }

    Api::class.java.getDeclaredMethod("getUsers")
        .genericReturnType.safeAs<ParameterizedType>()?.actualTypeArguments?.forEach {
        println(it)
    }

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

fun <T> Any.safeAs(): T? {
    return this as? T
}

例:为数据类实现 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 class
  • this::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前面返回的结果。

例:实现 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类型的,这时需要抛异常。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

0 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593761浏览

随机文章
Spring笔记4—复杂属性的注入
5年前
PGSQL—格式化时间的函数
5年前
Kotlin-内置类型—集合框架(五)
4年前
SpringMVC笔记7—Controller 方法的返回值
5年前
SpringBoot—启动时执行
3年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593761 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付