背景
首先还是来说下为什么出现元编程?
一个技术的出现肯定是不满足现状,那么元编程的出现是为了解决什么问题呢?举一个栗子,比如我们需要获取某个类的属性进行赋值取值或者获取函数信息进行调用时,我们当然可以编写代码以让外界访问这些数据,但是这样做容易出错而且特别麻烦,这个时候我们可以想到利用反射也可以达到同样的效果。对吧,获取类变量,函数信息这看起来就是反射可以做到的事情,所以其实反射也属于元编程范畴。
什么是元数据
顾名思义,元数据和元注解一个道理,元注解是标记注解的注解,元数据自然就是描述数据的数据,这个听起来有点绕口,来解释一些这两个“数据”到底分别指代什么?
描述“数据”的“数据”
我们知道我们的需求也就是程序是通过各种数据构建起来的,这些数据就是指类,函数,变量…等是对现实世界和需求的描述,这就是第一个数据的意思
通过类,变量,函数这些数据去描述需求程序
那么第二个数据也就知道了,是用来描述类,函数,变量的数据,这就是第二个数据的意思,也就是元数据。
通过元数据描述类,变量,函数信息
什么是元编程
直接说定义:操作元数据的编程就是指元编程。
比如我们通过反射获取类,属性,方法的一些信息,进而操作他们这也叫元编程。所以上面说到反射也算元编程的范畴。
但是这么说又太片面了,反射是通过程序获取数据,而元编程还包括通过数据获取程序。即“程序即是数据,数据即是程序”。
可以这么说元编程是更高阶的抽象,高阶函数用函数作为输入输出。而元编程用程序作为输入输出。
程序即是数据
这个很好理解,通过指定的程序来获取构成这个程序的信息,比如一个Book类,我们可以动态的获取这个类中的属性和行为,其实就是反射。
介绍
来看下Java中的反射信息结构
可以看到有参数,类,包这些信息,AccessibleObject信息代表的是可调用的元素。
比如Field指字段(仅仅代表字段),Excutable代表可执行其中包括构造函数和普通方法。
再来看下Kotlin中的反射结构:
Kclass代表类信息,Kparameter代表参数信息,而KCallable和Accessible一样代表的都是可调用的元素。
其分为两类,KFunction和KProperty,不同点是:
- KProperty中包含普通属性和可变属性KMutableProperty,且Kotlin中的属性包含Setter和Getter方法。。而java中的Field只代表这个字段,setget是在另外一个Method结构中
- KFunction统一了构造函数,包含Kproperty的Setter和Getter。而java中的Method还分为构造函数和普通函数,且是单独的setget方法不是Field自带的
- Java中反射需要设置可访问性,而Kotlin中的属性自带setget方法通过get可直接获取。也就是KProprity.call(对象实例)即可获取属性。
- Kotlin中获取信息比Java更明确直观。
Kotlin的增强
和java中的反射一样使用,不同的是Kotlin中由于多了很多特性所以其元数据类型也比java中多,比如:
metaclass描述类的类型kclass。
通过类名::class得到kclass
KClass中相比Java中的Class新增:
KCallable由于包含着KFunction和KProperty,所以先来看下KCallable中有哪些属性:
KCallable可通过KClass的members成员获取,其返回值是Collection<KCallable<*>>
通过上面的信息已经可以获取到了类,属性和方法的信息,那么我们该如何获取参数信息呢?
参数信息又分为这三种:方法的参数信息,方法的返回值信息,泛型的参数信息(也就是参数类型)。
可通过KCallable.parameters获取方法的参数信息,返回值是List< KParameter >。
KParameter新增属性:、
可看到通过Kparameter的type属性获取到参数的类型,那么返回值的类型和泛型类型该如何获取呢?
上面讲解KCallable的时候就已经有这两个属性了:
- 返回值类型:只有方法才有返回值,所以是通过KCallable的returntype属性可以获取到
- 参数类型:泛型一种是泛型方法还有一种是泛型类。泛型方法一样通过KCallable的typeParameters获取,在KClass中通过startProjectedType属性获取。返回值是List< KTypeParameter >不存在返回一个空的集合。
数据即是程序
这句话该怎么理解。我们倒推一下,通过一些信息来动态创建程序。
比如使用字节码工具ASM,javassist等动态生成类,还有使用KAPT注解处理器通过注解来手动输出程序到一个文件中。可以看到和Kotlin好像没有多大关系,所以Kotlin目前还没法做到动态创建程序。
注解处理器
Kotlin中的注解处理器和Java中的一样,注解参数为常量,作用范围为:
- 基本类型
- 字符串
- Class对象
- 注解
- 类型数组,XXXArray
定义方式:比Java中更明显:用annotation修饰类即可。
使用方式:
1.添加注解处理器信息。这需要在classpath里包含META-INFO/services/javax.annotation.processing.Processor文件,并将注解处理器包名和类名写入该文件。
2.使用kapt插件。如果是gradle工程可以通过apply plugin:'kotlin-kapt’添加注解处理器支持。
kapt也支持生成Kotlin代码。
缺点
虽然annotation processor允许开发人员访问程序AST(抽象语法树可查看之前文章JVM编译只是),但没有提供行之有效的代码生成方案,目前仅有的代码生成方案也仅仅是将代码以字符串的形式写入新文件,而无法做到直接将生成的AST作为程序。这也说明了Java和Kotlin目前不具备同像性。
元编程的使用范围
1.外部程序:kotlin的语法糖suger,最终会变成java文件。所以编译器承担了 解语法糖 的角色,编译器作为外部程序去操作这些语法糖(本质也是元数据)也叫作元编程
2.获取运行时数据(反射)
3.动态执行代码(目前无法做到)
元编程需要一定的学习成本,需要了解class结构和kclass等相关程序构成的数据。