回顾一下反射
很多框架和工具中,在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函数,每个类拥有的属性不一样。
- 违背DRY(do not repeat yourself)原则
- 很容一些错属性名
用反射试一下
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)
}
- 适用所有data class
- 不再需要手动创建map。KClass 对象获取减少了使用的错误
7.1 程序和数据,什么是元编程
a::class 可以看成描述User类型的数据,这样描述数据的数据称之为元数据
。
操作元数据的编程,叫做元编程
「程序就是数据,数据就是程序」
- 访问描述程序的数据---如通过反射获取类型信息
- 将这些数据转化成对应的程序---代码生成
仔细思考,元编程就像高阶函数一样,是一种高阶抽象,高阶函数将函数作为输入输出,元编程将程序本身
作为输入输出
常见元编程
- 运行时通过API暴露程序信息。反射就是这个实现思路
- 动态执行代码。比较代表性的是JavaScript的eval函数。
- 通过外部程序实现。如编译器,源文件解析成AST,针对AST做各种转化。也就是我们说的语法糖。编译器将语法糖AST转换成相应等加的AST,这样叫做解语法糖。
- 反射,也叫做自反。(描述程序的数据结构和要描述的语言)
- 宏,C语言编译器的预处理。 Kotlin 暂时不支持
- 模板元变成。C++ 招牌菜,Kotlin无关
- 路径依赖类型。Haskell,Scala有涉及。Kotlin 无关
7.2 Kotlin的反射
Kotlin和Java对比
2020-12-30 15.16.10.png- Kotlin 的KClass和Java的Class,可以看做同一个含义的类型,并且可以通估.kotlin和.java 方法在KClass和Class之间转化
- Kotlin 的KCallable 和 Java的AccessiableObject 都可以理解为可调用的元素。java构造方法为一个独立类型,Kotlin统一作废KFunction处理
- 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;
}
- 相对Kotlin更直观简洁,java例子多了很多额为元素,创建一个Map,Stream的forEach遍历,处理可能的异常
- Java直接强制访问字段设置访问权限。Kotlin的call实际上是直接调用Getter
KClass 特别的属性
属性或者函数名 | 含义 |
---|---|
declaredMemberExtensionProperties | 本类及超雷扩展属性 |
declaredMemberExtensionFunctions | 本类及超类扩展函数 |
memberExtensionProperties | 扩展属性 |
memberExtensionFunctions | 扩展函数 |
isCompanion | 是否是半生对象 |
isData | 是否数据类 |
isSealed | 是否密封类 |
objectInstance | object实例(如果是object) |
starProjectedType | 泛型统配类型 |
declaredMemberExtensionFunctions 获取的是类中声明的方法,无法获取类外所有的扩展
Kotlin的KCallable
有时候我们不只有想获取类的属性,还要更改他的值。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
网友评论