Kotlin学习手记——反射

文章详细介绍了Kotlin中如何通过不同方法获取接口的泛型类型,如使用Kotlin反射、Java反射和函数引用,并展示了如何在数据类中实现DeepCopy功能以及模型映射。此外,还提到了处理不可空类型和面试技巧相关内容。
摘要由CSDN通过智能技术生成

//获取到 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 然后可以调用java的反射方法获取泛型类型

Api::class.java.getDeclaredMethod(“getUsers”)

.genericReturnType.safeAs()?.actualTypeArguments?.forEach {

println(it)

}

//safeAs是定义的一个Any的扩展方法

fun 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 {

//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()!!.actualTypeArguments.first()

}

}

open class SubType : SuperType()

获取上面 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 {

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()

class SubType2: SubType()

获取上面 SubType2类的父类实现的SuperType接口类的泛型:

val subType2 = SubType2()

subType2.typeParameter2.let(::println) // kotlin.String

实例:为数据类实现 DeepCopy

fun 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)逆变转协变

val value = (this::class as KClass).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前面返回的结果。

这个例子有一个完整的开源库代码 KotlinDeepCopy 是由我参阅的学习资料的大神Bennyhuo所写的,但是貌似目前没什么issue, 慎用,可以当案例学习一下。

实例:实现 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 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 releasableNotNull() = ReleasableNotNull()

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)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档

喜欢的朋友可以关注、转发、点赞 感谢!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

U-1711865235991)]

[外链图片转存中…(img-3xr2TU4y-1711865235991)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档

[外链图片转存中…(img-pOBCwBED-1711865235992)]

[外链图片转存中…(img-sahAwoLF-1711865235992)]

喜欢的朋友可以关注、转发、点赞 感谢!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值