移动 前端 Python Android Java我的Kotlin之旅Android · Kotlin · 移动开发 · 性能测试 · 无线技术

Kotlin(七)元编程

2020-12-30  本文已影响0人  zcwfeng

回顾一下反射

很多框架和工具中,在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类型的数据,这样描述数据的数据称之为元数据
操作元数据的编程,叫做元编程
「程序就是数据,数据就是程序」

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

常见元编程
  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

上一篇 下一篇

猜你喜欢

热点阅读