Kotlin(七)元编程

作者: zcwfeng | 来源:发表于2020-12-30 17:08 被阅读0次

    回顾一下反射

    很多框架和工具中,在Java领域你会看到很多反射的影子,Java的反射只是元编程的一种方式。

    看一个问题,将data class 转换成Map的例子:

    // data class 转换 Map
    data class User(val name:String,val age:Int)
    
    object UserToMap{
        fun toMap(a:User):Map<String,Any>{
            return hashMapOf("name" to a.name,"age" to a.age)
        }
        
    }
    

    这样,如果加入一个新的类型,就需要我们重新复现toMap函数,每个类拥有的属性不一样。

    1. 违背DRY(do not repeat yourself)原则
    2. 很容一些错属性名

    用反射试一下

    object Mapper {
        fun <A : Any> toMap(a: A): Map<String, Any?> {
            return a::class.memberProperties
                .map { m ->
                    val p = m as KProperty<*>
                    p.name to p.call(a)
                }.toMap()
        }
    }
    
    fun main() {
        val map = Mapper.toMap(User("David", 18))
        println(map)
    }
    
    
    1. 适用所有data class
    2. 不再需要手动创建map。KClass 对象获取减少了使用的错误

    7.1 程序和数据,什么是元编程

    a::class 可以看成描述User类型的数据,这样描述数据的数据称之为元数据
    操作元数据的编程,叫做元编程
    「程序就是数据,数据就是程序」

    • 访问描述程序的数据---如通过反射获取类型信息
    • 将这些数据转化成对应的程序---代码生成

    仔细思考,元编程就像高阶函数一样,是一种高阶抽象,高阶函数将函数作为输入输出,元编程将程序本身作为输入输出

    常见元编程
    • 运行时通过API暴露程序信息。反射就是这个实现思路
    • 动态执行代码。比较代表性的是JavaScript的eval函数。
    • 通过外部程序实现。如编译器,源文件解析成AST,针对AST做各种转化。也就是我们说的语法糖。编译器将语法糖AST转换成相应等加的AST,这样叫做解语法糖。
    1. 反射,也叫做自反。(描述程序的数据结构和要描述的语言)
    2. 宏,C语言编译器的预处理。 Kotlin 暂时不支持
    3. 模板元变成。C++ 招牌菜,Kotlin无关
    4. 路径依赖类型。Haskell,Scala有涉及。Kotlin 无关

    7.2 Kotlin的反射

    Kotlin和Java对比
    2020-12-30 15.16.10.png
    1. Kotlin 的KClass和Java的Class,可以看做同一个含义的类型,并且可以通估.kotlin和.java 方法在KClass和Class之间转化
    2. Kotlin 的KCallable 和 Java的AccessiableObject 都可以理解为可调用的元素。java构造方法为一个独立类型,Kotlin统一作废KFunction处理
    3. Kotlin的KProperty 和 Java的Field 有点不同。Kotlin的KProperty通常指Getter和Setter,整体作为KProperty。Java Field就是某个字段

    Java 反射

    public static <A> Map<String,Object> toMap(A a){
            Field[] fs = a.getClass().getDeclaredFields();
            Map<String,Object> kvs = new HashMap<>();
            Arrays.stream(fs).forEach(f ->{
              f.setAccessible(true);
                try {
                    kvs.put(f.getName(),f.get(a));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            });
            return kvs;
        }
    
    1. 相对Kotlin更直观简洁,java例子多了很多额为元素,创建一个Map,Stream的forEach遍历,处理可能的异常
    2. Java直接强制访问字段设置访问权限。Kotlin的call实际上是直接调用Getter
    KClass 特别的属性
    属性或者函数名 含义
    declaredMemberExtensionProperties 本类及超雷扩展属性
    declaredMemberExtensionFunctions 本类及超类扩展函数
    memberExtensionProperties 扩展属性
    memberExtensionFunctions 扩展函数
    isCompanion 是否是半生对象
    isData 是否数据类
    isSealed 是否密封类
    objectInstance object实例(如果是object)
    starProjectedType 泛型统配类型

    declaredMemberExtensionFunctions 获取的是类中声明的方法,无法获取类外所有的扩展

    Kotlin的KCallable

    官网API

    有时候我们不只有想获取类的属性,还要更改他的值。Java通过Field.set(...) Kotlin中不是所有的属性都是可以更改的。因此我们只能通过更改可变的属性进行修改操作。KMutableProperty是KProperty的一个子类,如何区分KMutableProperty和KProperty,使用when表达式

    fun KMutablePropertyShow(){
        val p = Person2("David",8,"HangZhou")
        val props = p::class.memberProperties
        for (prop in props){
            when(prop){
                is KMutableProperty<*> -> prop.setter.call(p,"Beijng")
                else -> prop.call(p)
            }
        }
        println(p.address)
    }
    
    获取参数信息KParameter,KType,KTypeParameter
    fun KParameterShow(){
        val p = Person2("David",8,"HangZhou")
        for (c in p::class.members){
            println("${c.name}->")
            for (p in c.parameters){
                print("${p.type} -- ")
            }
            println()
        }
    
    }
    

    KType

    API 描述
    arguments:List<KTypeProjection> 该类型的类型参数
    classfier:KClassfier? 该类型在类声明层的类型,如该类型List<String>,那么通过classfier获取结果List(忽略类型参数)
    isMarkedNullable 该类型是否标记为可空类型
    data class Person2(val name:String,val age:Int,var address:String){
        fun friendName():List<String>{
            return listOf("zhangsan","lisi")
        }
    }
    Person2::class.members.forEach {
            println("${it.name} -> ${it.returnType.classifier}")
        }
    
    >>>>>输出
    address -> class kotlin.String
    age -> class kotlin.Int
    name -> class kotlin.String
    component1 -> class kotlin.String
    component2 -> class kotlin.Int
    component3 -> class kotlin.String
    copy -> class top.zcwfeng.kt.base.Person2
    equals -> class kotlin.Boolean
    friendName -> class kotlin.collections.List
    hashCode -> class kotlin.Int
    toString -> class kotlin.String
    

    KTypeParameter

    data class Person2(val name:String,val age:Int,var address:String){
        fun friendName():List<String>{
            return listOf("zhangsan","lisi")
        }
    
        fun <A> get(a:A):A{
            return a
        }
    }
    fun  KTypeParameterShow(){
        for (c in Person2::class.members){
            if(c.name.equals("get")){
                println(c.typeParameters)
            }
        }
    
        val list = listOf("How")
        println(list::class.typeParameters)
    }
    
    >>>>>>
    
    [A]
    [E]
    

    7.3 Kotlin 注解

    Kotlin 创建注解非常简单在class 前面添加annotation关键字

    annotation class FooAnnotation(val bar:String)
    
    Kotlin Java
    kotlin.annotation.Retention java.lang.annotation.RetentionPolicy
    kotlin.annotation.Target java.lang.annotation.Target
    kotlin.annotation.Documented java.lang.annotation.Documented
    kotlin.annotation.Repeatable java.lang.annotation.Repeatable

    看一个例子

    annotation class Cache(val namespace:String,val expires:Int)
    annotation class CacheKey(val keyName:String,val buckets:IntArray)
    data class Hero(
        @CacheKey(keyName = "heroName",buckets = intArrayOf(1,2,3))
        val name:String,
        val attack:Int,
        val defense:Int,
        val initHp:Int
    )
    

    精确注解控制

    @Cache(namespace = "hero",expires = 3600)
    data class Hero2(
        @property:CacheKey(keyName = "heroName",buckets = intArrayOf(1,2,3))
        val name:String,
        @field:CacheKey(keyName = "atk",buckets = intArrayOf(1,2,3))
        val attack:Int,
        @get:CacheKey(keyName = "def",buckets = intArrayOf(1,2,3))
        val defense:Int,
        val initHp:Int
    )
    

    获取信息

        val cacheAnnotation = Hero2::class.annotations.find {
            it is Cache
        }as Cache?
        println("namespaces ${cacheAnnotation?.namespace}")
        println("expires ${cacheAnnotation?.expires}")
    
    
    注解处理器

    编译器工作


    2020-12-30 17.01.09.png

    注解处理器使用方法和java一样
    1)添加注解处理器信息。需要在classpath里包含META-INFO/services/javax.annotation.processing.Processor文件,并经注解处理器的包名类名写入该文件
    2)使用kapt插件,如果是gradle工程可以用过apply plugin:‘kotlin-kapt’添加注解处理器支持

    目前仅有的代码生成方案将字符串形式写入新的文件
    还有一种方式借助第三方,kapt poet

    参照github kapt poet

    相关文章

      网友评论

        本文标题:Kotlin(七)元编程

        本文链接:https://www.haomeiwen.com/subject/abhhoktx.html