反射的基本概念
反射(Reflection) 是一种强大的编程技术,允许在运行时动态地访问、检测和修改程序的结构(例如类、方法、字段等)。在 Java 和 Kotlin 中,反射可以用于在运行时获取对象的类型信息、调用方法、访问字段等,这些操作通常在编译时是不可知的。
①KType是获取未擦除的实际类型
//获得Type类型
typeOf<Map<String,Int>>()
②KClass是获取实际的类型,不包含泛型参数
//获取的是KClass
val cls:KClass<String> = String::class
③Kproperty是获取属性,包括field、getter、setter
④KFunction是获取函数的引用
获取class,是Kclass类型,要换成java的class需要转换:
//如果要换成java的class
cls.java.kotlin
获取泛型实参
interface Api{
fun getUsers():List<UserData>
}
abstract class SuperT<T>{
//获取子类
val typeParameter by lazy {
this::class.supertypes.flatMap {
it.arguments.mapNotNull {
it.type?.classifier as? KClass<*>
}.filter {
it == String::class
}
}
}
//用javaClass获取子类的类型
val typeParameterJava by lazy {
this.javaClass.genericSuperclass.safeAs<ParameterizedType>()!!
.actualTypeArguments.first()
}
}
class SubType:SuperT<String>()
fun main(){
/*
先拿到反射的类,再拿到函数,筛选出为"getUsers"
因为可能有多个"getUsers",所以存入map,再拿到函数里面的参数,打印出来
*/
Api::class.declaredFunctions.filter { it.name =="getUsers" }
.map {it.returnType.arguments.forEach(::println) }
//用类直接反射到函数后拿到函数里的参数。
println(Api::getUsers.returnType.arguments.forEach (::println))
//用java的反射,拿到函数里面的参数。
Api::class.java.getDeclaredMethod("getUsers")
.genericReturnType.safeAs<ParameterizedType>()?.
actualTypeArguments?.forEach(::println)
val subT = SubType()
subT.typeParameter.let (::println )
}
inline fun <reified T>Any.safeAs():T? {
return this as? T
}
反射实现数据类的DeepCopy
例子:
fun <T : Any> T.deepCopy(): T {
// 如果类不是数据类,则直接返回原对象,因为只有数据类才需要进行深复制。
if(!this::class.isData){
return this
}
// 获取数据类的主构造函数并通过反射生成一个新的实例。
return this::class.primaryConstructor!!.let { primaryConstructor ->
// 遍历主构造函数的参数列表,为每个参数获取对应的属性值。
primaryConstructor.parameters.map { parameter ->
// 通过参数名称找到对应的属性,并获取其值。
val value = (this::class as KClass<T> ).memberProperties.first { it.name == parameter.name }
.get(this)
// 如果参数的类型也是一个数据类,那么递归调用 deepCopy 进行深复制。
if((parameter.type.classifier as? KClass<*>)?.isData == true){
// 创建一个新的键值对,键是参数,值是深复制后的值。
parameter to value?.deepCopy()
} else {
// 如果参数类型不是数据类,则直接使用原始值。
parameter to value
}
}
// 将参数和值映射成键值对(Map),并传递给主构造函数生成新的实例。
.toMap()
.let(primaryConstructor::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,
"AidenAn",
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:浅复制的对象内部引用仍然指向同一个对象。
println(person.group === deepCopiedPerson.group) // false:深复制的对象内部引用指向不同的对象。
println(deepCopiedPerson) // 打印深复制后的对象内容。
}
Model映射
data class UserVO(val login:String,val avatarUrl:String)
data class UserDTO(
val id:Int,
val login: String,
val avatarUrl: String,
val url: String,
val htmlURL: String
)
inline fun <reified T:Any>Map<String,Any?>.mapAs():T{
return T::class.primaryConstructor!!.let{
//拿到T类型中的所有属性
it.parameters.map {paramenter->
//检查属性的值是否在Map中存在,不存在返回空。如果有不可空类型又为空就报错
paramenter to (this[paramenter.name]?:if (paramenter.type.isMarkedNullable) null
else throw IllegalArgumentException("${paramenter.name}is required but missing.") )
}.toMap()
.let (it::callBy)
}
}
inline fun <reified T:Any,reified To:Any> T.mapAS():To{
return T::class.memberProperties.map { it.name to it.get(this) }
.toMap().mapAs()
}
fun main(){
val userDTO = UserDTO(
0,
"Bennyhuo",
"https://avatars2.githubusercontent.com/u/30511713?v=4",
"https://api.github.com/users/bennyhuo",
"https://github.com/bennyhuo"
)
//隐式推导mapAS<UserDTO,UserVO>
val userVO:UserVO = userDTO.mapAS()
println(userVO)
val userMap = mapOf(
"id" to 0,
"login" to "AidenAn",
"avatarUrl" to "https://api.github.com/users/bennyhuo",
"url" to "https://api.github.com/users/bennyhuo"
)
val userVoFromMap:UserVO= userMap.mapAs()
println(userVoFromMap)
}
可释放对象引用的不可空类型
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 IllegalArgumentException("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
}
}
//isInitialized会经常调用,检查是否初始化。所以用inline,减少内存开销
inline val KProperty0<*>.isInitialized:Boolean
get() {
isAccessible = true
return (this.getDelegate() as? ReleasableNotNull<*>)?.isInitialized()
?:throw IllegalAccessException("Delegate is not an instance of ReleasableNotNull or is null.")
}
//不用inline,是因为访问的次数不多,并且用inline会增加字节码的大小
fun KProperty0<*>.release(){
isAccessible=true
(this.getDelegate() as? ReleasableNotNull<*>)?.release()
?: throw illegalDecoyCallException("Delegate is not an instance of ReleasableNotNull or is null.")
}
class Bitmap(val width:Int,height:Int)
class Activity{
private var bitmap by releasableNotNull<Bitmap>()
fun onCreate(){
println(this::bitmap.isInitialized)
bitmap = Bitmap(1920,4000)
println(this::bitmap.isInitialized)
}
fun onDestroy(){
println(::bitmap.isInitialized)
::bitmap.release()
println(::bitmap.isInitialized)
}
}
fun main(){
val activity = Activity()
activity.onCreate()
activity.onDestroy()
}
注解的基本使用
注解(Annotation)在 Kotlin 中是一种元数据,用于为代码添加额外的信息,这些信息可以由编译器、运行时或者其他工具使用。
注解可以应用于类、函数、属性、参数等,并且 Kotlin 与 Java 共享相同的注解机制,因此可以在 Kotlin 中使用现有的 Java 注解。
①限定标注的对象(@Target)
可以是CLASS、FUNCTION、TYPE....等等
:
@Target(AnnotationTarget.CLASS)
annotation class MyAnnotation(val ds:String)
②定义注解保留时长(@Retention)
时机总共有三个
SOURCE < BINARY < RUNTIME。
SOURCE表示源码时候获取到。BINARY 表示编译期获取到。RUNTIME表示运行时获取到。
他们之间是有包含关系,比如定义为BINARY,那么源码和编译期一定能获取到。
:
@Retention(AnnotationRetention.SOURCE)
annotation class MyAnnotation(val ds:String)
③要定义一个注解类,在类的前面加上关键字 annotation就可以了。
annotation class MyAnnotation(val ds:String)
④注解类的构造函数可以定义参数:
@Target(AnnotationTarget.FUNCTION) //如果把Target定义成CLASS,就无法在下面的方法使用。
@Retention(AnnotationRetention.SOURCE)
annotation class MyAnnotation(val ds:String)
@MyAnnotation(ds = "this is a test function")
fun testFun(){
println("H W")
}
⑤注意参数的类型,因为注解有可能是在源码时,源码中没有我们自定义的类。
所以参数的类型支持以下类似:
基本类型、KClass、枚举、其他注解。
常见内置注解的使用
①标注注解
@Deprecated标记一个元素不再使用,可以提供代替方案或者原因。
例如:
@Deprecated("Use newF instead",ReplaceWith("newF()"))
fun abandonF(){}
@Target 指定注解使用的类型。
@Retention 控制注解保留的时长
例如:
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class MyAnnotation(val ds:String)
②通用注解
Kotlin中的通用注解通常用于与Java、其他库或工具进行交互,常见的有:
@Suppress:用于抑制编译器警告。参数是警告的名称.
例如:
@Suppress("DEPRECATION")
fun usedF(){
abandonF()
}
@JvmOverloads:用于生成多个重载函数,特别是在Kotlin与Java代码混合时适用。Kotlin支持默认参数,而Java不支持,通过此注解可以为每个默认参数生成一个Java的重载版本。
@JvmStatic:用于将Kotlin对象中的方法静态化,使其在Java代码中可以像静态方法一样调用。
③java虚拟机相关注解
@JvmName:用于修改在字节码中生成的类、方法或字段的名称,通常用于避免方法签名冲突或提供更清晰的名称。
例如:
@JvmName("a")
fun setJvmName(){}
@JvmMultifileClass:用于将多个Kotlin文件编译成一个单独的Java类文件。这通常与 @file:JvmName 注解一起使用,以指定生成的Java类的名称。
@Throws:指定函数在JVM上可能抛出的异常,通常用于Kotlin与Java交互时,明确地告诉Java代码可能的异常类型。
例如:
@Throws(IOException::class)
fun readFile(file: File): String {
if (!file.exists()) throw IOException("File not found")
return file.readText()
}
@Synchronized:它为 Kotlin 方法或者函数提供同步机制,确保在多线程环境下只有一个线程可以访问被标注的代码块或函数。这个注解的作用是将方法同步化,以避免多个线程同时访问共享资源时出现数据不一致的问题。
注解加持反射Models
通常我们在访问数据库的时候,数据库的命名会带有下划线。
而在类里面,通常使用的是驼峰。
用下面的这个框架就可以简化映射,自动转换下划线和驼峰的情况。
①可以直接使用注解,标注名称
②使用注解策略,标注类
例子如下:
//这是第①种方法,实现映射
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class FiledName(val name:String)
//这是第②种方法,添加注解策略
@Target(AnnotationTarget.CLASS)
annotation class MappingStrategy(val klass:KClass<out NameStrategy >)
//把一个name,从一个String类型,转换成另一个String类型
interface NameStrategy{
fun mapTo(name:String):String
}
//下划线转驼峰。例如:html_url ->htmlUrl
object UnderLTransformHump:NameStrategy{
override fun mapTo(name: String): String {
/*fold是一个迭代元素进行累加的方法
第一个参数是初始值,第二个参数是累加函数。
累计函数里面中的acc是累加器,c是当前元素。*/
return name.toCharArray().fold(StringBuilder()){
acc, c ->
when(acc.lastOrNull()){
'_' -> acc[acc.lastIndex]= c.uppercaseChar()
else -> acc.append(c)
}
acc
}.toString()
}
}
//同理,驼峰转下划线
object HumpTransformUnderL:NameStrategy{
override fun mapTo(name: String): String {
return name.toCharArray().fold(StringBuilder()){
acc, c ->
when{
c.isUpperCase() ->acc.append('_').append(c.lowercaseChar())
else -> acc.append(c)
}
acc
}.toString()
}
}
data class UserVO(val login:String,
@FiledName("avatar_url")
val avatarUrl:String
)
data class UserDTO(
val id:Int,
val login: String,
val avatar_url: String,
val url: String,
val htmlURL: String
)
inline fun <reified T:Any>Map<String,Any?>.mapAs():T{
return T::class.primaryConstructor!!.let{
//拿到T类型中的所有属性
it.parameters.map {paramenter->
//检查属性的值是否在Map中存在,不存在返回空。如果有不可空类型又为空就报错
paramenter to (this[paramenter.name]
?:(paramenter.findAnnotations<FiledName>().firstOrNull()?.name?.let (this::get))
?: T::class.findAnnotation<MappingStrategy>()?.klass?.objectInstance?.mapTo(paramenter.name!!)
?:if (paramenter.type.isMarkedNullable) null
else throw IllegalArgumentException("${paramenter.name}is required but missing.") )
}.toMap()
.let (it::callBy)
}
}
inline fun <reified T:Any,reified To:Any> T.mapAS():To{
return T::class.memberProperties.map { it.name to it.get(this) }
.toMap().mapAs()
}
fun main(){
val userDTO = UserDTO(
0,
"Bennyhuo",
"https://avatars2.githubusercontent.com/u/30511713?v=4",
"https://api.github.com/users/bennyhuo",
"https://github.com/bennyhuo"
)
//隐式推导mapAS<UserDTO,UserVO>
val userVO:UserVO = userDTO.mapAS()
println(userVO)
val userMap = mapOf(
"id" to 0,
"login" to "AidenAn",
"avatarUrl" to "https://api.github.com/users/bennyhuo",
"url" to "https://api.github.com/users/bennyhuo"
)
val userVoFromMap:UserVO= userMap.mapAs()
println(userVoFromMap)
}
注解处理器和编译器插件
注解处理器不会在JVM上动态生成.java文件,只有在编译时生成新的.java文件。然后由编译器将这些编译成.class文件。
编译器插件会动态生成.java文件,通过字节的方式修改。不会产生新的.java文件。
协程的基本概念
①协程的定义
协程就是实现挂起跟恢复
②协程的作用
用异步的逻辑,写出来的代码是同步的,可以优化代码
③协程的异步机制
挂起,等待回调后执行。是非阻塞挂起。
④协程与线程对比
线程指操作系统的线程
协程指语言实现的协程
⑤协程的学习方式
协程比较有难度,需要循序渐进。
协程的分类
①按调用栈分类:
有栈协程:可以在任意函数嵌套中挂起,例如 Lua 语言中的Coroutine
无栈协程:只能在当前函数中挂起,例如 Python 语言中的Generator
②按调用关系分类:
对称协程:调度权可以转移给任意协程,协程之间是对等关系
非对称协程:调度权只能转移给调用自己的协程,协程存在父子关系
③各种语言中的协程:
Python中的Generator 属于无栈的非对称协程
Lua 中的Coroutine 属于有栈的非对称协程
Go 中的routine 可以认为是一种有栈对称协程的实现(虽然开发者不承认是协程)
但是Go 中的routine 确实有挂起和恢复操作,可以看作协程。
JS中的 async /await 属于无栈非对称协程
Kotlin中的suspend实际上就是async /await
另外,有栈和无栈的区别在于,有栈会开辟内存空间。
协程的基本要素
①挂起函数的调用处称为挂起点。
②挂起函数:
用suspend修饰的函数
挂起函数只能在其他挂起函数或协程中调用(也就是无栈协程)
挂起函数调用时包含“挂起”
挂起函数返回时包含“恢复”
③挂起函数的实际类型:
//bar的挂起函数和bar1是一样的。
suspend fun bar(a:Int):String{
return ""
}
//Continuation中的泛型参数是挂起参数的返回类型
fun bar1(a:Int ,continuation: Continuation<String>):Any{
return ""
}
Any返回类型有两种,一种是返回挂起标记Coroutine_Suspended,属于真正的挂起
另外一种是没有正在的挂起直接return返回真正的结果。
④真正的挂起必须异步调用resume,包括:
切换到其他线程resume。
单线程事件循环异步执行
⑤创建协程的两种方式
用createCoroutine 然后在返回的Continuation后 .resume(Unit)
、
还可以用startCoroutine,就可以省略最后的.resume(unit)
⑥拦截器
可以对协程上下文所在的协程的Continuation进行拦截
SafeContinuation对于SuspendLambda的包装,确保只被调用一次,如果没有新线程不会被挂起。
Intercepted实际上,也是对SuspendLambda的包装,在每次恢复或第一次执行的时候调用。
协程框架概述
①框架的基本情况:
②协程框架的引入:
③协程的启动模式:
立即开始调度协程体的意思是指,Launch协程后,返回后执行自己。
第一种是DEFAULT:
立即开始调度,调度前可以直接取消。:
第二种是ATOMIC:
立即开始调度,在第一个挂起点前不能取消。:
第三种是LAZY
创建之后不会立即执行,等到start/join/await执行后,才会执行协程体。
第四种是UNDISPATCHED:
立即执行协程体,直到遇到第一个挂起点后,回到主调用流程。
Channel
Channel的概念:
非阻塞的通信基础设施,类似于BlockingQueue + 挂起函数。
Channel的分类:
恭喜你,已经完成了kotlin的全部知识点。接下来就可以运用到实践与生产中去了。
可以关注我github上的一些开源库:AidenHZ (github.com)