kotlin进阶知识点(下篇)

反射的基本概念


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值