Kotlin(七)元编程

回顾一下反射

很多框架和工具中,在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

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容