注解是将元数据附加到代码的地方。从字面意思理解它就是对知识点的补充,一种描述。在Java中最常见的注解就是@Override
或者就是Retrofit中的@GET
、@POST
等。
创建时用annotation
修饰符进行声明,如
annotation class GET()
这样就创建好了一个注解,但是这里想要完全使用还要添加一些属性:
这里重点要注意的是 @Target和@Retention
//@Target public enum class AnnotationTarget { // 类、接口、object、注解类 CLASS, // 注解类 ANNOTATION_CLASS, // 泛型参数 TYPE_PARAMETER, // 属性 PROPERTY, // 字段、幕后字段 FIELD, // 局部变量 LOCAL_VARIABLE, // 函数参数 VALUE_PARAMETER, // 构造器 CONSTRUCTOR, // 函数 FUNCTION, // 属性的getter PROPERTY_GETTER, // 属性的setter PROPERTY_SETTER, // 类型 TYPE, // 表达式 EXPRESSION, // 文件 FILE, // 类型别名 TYPEALIAS } //@Retention public enum class AnnotationRetention { // 注解只存在于源代码,编译后不可见 SOURCE, // 注解编译后可见,运行时不可见 BINARY, // 编译后可见,运行时可见 默认 RUNTIME }
@Target(AnnotationTarget.FUNCTION) //注解用于方法 @Retention(AnnotationRetention.RUNTIME) //运行时可见,编译时可见 annotation class Custom() //正常使用不报错 @Custom fun test() { println("") } //报错,因为注解不支持Class,如果要支持就需要在@Target里面加上AnnotationTarget.CLASS @Custom class Test{ }
上面的代码是一个自定义且最简单的一个用法,现在看一下Kotlin中自带的一个注解,这个注解用来标注废弃的方法或者类等定义,比较常见
@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS) @MustBeDocumented public annotation class Deprecated( val message: String, val replaceWith: ReplaceWith = ReplaceWith(""), val level: DeprecationLevel = DeprecationLevel.WARNING )
@Tageget:支持类、 函数、 属性、注解类、构造器、属性 getter、属性 setter、类型别名
在Deprecated
内部还传递了几个参数:
ReplaceWith
也是一个注解,也就是说Kotlin中注解中是可以添加注解的,只不过添加时不可以用@
。WARNING
、ERROR
、HIDDEN
注解中允许的参数有:
注解参数不能有可空类型,因为 JVM 不支持将 null 作为注解属性的值存储。
反射是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
反射在业务开发中用的较少,主要是在架构设计中,可以极大地提升架构的灵活性。
Kotlin的反射具备三个特点:
首先要加入一个依赖才可以使用反射
implementation "org.jetbrains.kotlin:kotlin-reflect"
然后根据上面提到的三个特点进行讲解:
举例:假设现在有两个对象,在不传递具体对象的前提下想要打印出他们每一个属性的名称以及具体的值
fun main() { val person = Person("张三", 22) val animal = Animal("猫", "用脚走", "猫粮") findClassAttribute(person) findClassAttribute(animal) } fun findClassAttribute(obj: Any) { } data class Person(val name: String, var age: Int) data class Animal(var species: String, val walkWay: String, var food: String) //期望结果: //Person.name = 张三 //Person.age = 22 //Animal.species = 猫 //Animal.walkWay = 用脚走 //Animal.food = 猫粮
上面只是定义了两个类,具体项目中可能会很有很多的类,因此用when的方式是行不通的因为这样工作量还是比较大的,那么用反射反而是一个比较好的方式,那要如何实现?
fun findClassAttribute(obj: Any) { obj::class.memberProperties.forEach { println("${obj::class.simpleName}.${it.name} = ${it.getter.call(obj)}") } } //输出结果 //Person.age = 22 //Person.name = 张三 //Animal.food = 猫粮 //Animal.species = 猫 //Animal.walkWay = 用脚走
看到这个是不是一脸懵?这是啥鬼东西。我们对上面的代码进行分析就明白了:
Person
和Animal
。obj::class
拿到了具体的类型,那么也就拿到了这个这个类型的所有信息,比如说simpleName
、constructors
,而memberProperties
就是获取类的成员属性,然后通过foreach
遍历出来就好了。it.name
拿到属性的命名,it.getter.call
拿到属性的值。经过上述几个关键信息就获取到了我们想要的输出结果,这就是感知程序的状态。
拿到每个属性的命名和值之后我想要修改动物类的某个属性的值怎么办?增加一个changeClassAttributeValue
方法用来修改属性值
fun changeClassAttributeValue(obj: Any) { obj::class.memberProperties.forEach { if (it.name == "food" //判断要修改的属性名是【food】 && it is KMutableProperty1 //判断这个属性是否可以被修改,val属性不可被修改,var属性可以 && it.setter.parameters.size == 2 //修改属性需要setter,我们要先判断 setter 的参数是否符合预期,这里 setter 的参数个数应该是 2,第一个参数是 obj 自身,第二个是实际的值 && it.getter.returnType.classifier == String::class //判断要修改的属性是不是string类型 ) { it.setter.call(obj, "鸡胸肉") //修改属性值 println("========属性值修改========") } } } fun main() { val person = Person("张三", 22) val animal = Animal("猫", "用脚走", "猫粮") changeClassAttributeValue(animal) findClassAttribute(animal) } //输出结果 //Animal.food = 鸡胸肉 //Animal.species = 猫 //Animal.walkWay = 用脚走
根据属性值food
将猫粮修改为鸡胸肉。 这种操作方式就是反射的第二个特点:修改程序的状态
上面我们已经调整状态了,那么我还想加一个修改属性:species
,吃鸡胸肉的也可以是小狗,因此我们只需要再加一个else即可,这样就实现了最后一个特点:根据程序的状态,调整自身的决策行为。
fun changeClassAttributeValue(obj: Any) { obj::class.memberProperties.forEach { if (it.name == "food" //判断要修改的属性名是【food】 && it is KMutableProperty1 //判断这个属性是否可以被修改 && it.setter.parameters.size == 2 //修改属性需要setter,我们要先判断 setter 的参数是否符合预期,这里 setter 的参数个数应该是 2,第一个参数是 obj 自身,第二个是实际的值 && it.getter.returnType.classifier == String::class //判断要修改的属性是不是string类型 ) { it.setter.call(obj, "鸡胸肉") println("======== food 属性值修改========") } else if (it.name == "species" //判断要修改的属性名是【species】 && it is KMutableProperty1 //判断这个属性是否可以被修改,val属性不可被修改,var属性可以 && it.setter.parameters.size == 2 //修改属性需要setter,我们要先判断 setter 的参数是否符合预期,这里 setter 的参数个数应该是 2,第一个参数是 obj 自身,第二个是实际的值 && it.getter.returnType.classifier == String::class //判断要修改的属性是不是string类型 ) { it.setter.call(obj, "小狗") println("======== species 属性值修改========") } else { // 差别在这里 println("没找到相关属性") } } } fun main() { val person = Person("张三", 22) val animal = Animal("猫", "用脚走", "猫粮") changeClassAttributeValue(animal) findClassAttribute(animal) } //输出结果 //Animal.food = 鸡胸肉 //Animal.species = 小狗 //Animal.walkWay = 用脚走
这里还要说明的是可修改的属性值一定是用var
修饰的,如果在demo过程中出现不能修改的要检查属性声的明是否可修改。
上面的代码通过memberProperties
进入之后可以发现它用到了 Kotlin 反射的几个关键类:KClass、KCallable、KParameter、KType。现在,我们来进一步看看它们的关键成员
KClass 代表了一个 Kotlin 的类,下面是它的重要成员:
Collection<KCallable<*>>
;Collection<KFunction<T>>
;Collection<KClass<*>>
;List<KTypeParameter>
;List<KType>
;List<KClass<out T>>
;final
类型,类型为Boolean,如果是则为true;open
修饰的,类型为Boolean,如果是则为true;abstract
的,类型为Boolean,如果是则为true;KCallable 代表了 Kotlin 当中的所有可调用的元素,比如函数、属性、甚至是构造函数。下面是 KCallable 的重要成员:
构造函数名称为"";
属性访问器:一个名为foo的属性getter将会有名称,同理setter将会有名称
List<KParameter>
;KType
;List<KTypeParameter>
;final
修饰的,类型为Boolean,如果是则为true;open
修饰的,类型为Boolean,如果是则为true;abstract
修饰的,类型为Boolean,如果是则为true;KParameter,代表了KCallable当中的参数,它的重要成员如下:
KType
;INSTANCE: 对象的实例;
EXTENSION_RECEIVER: 扩展接收者;
VALUE: 具体的值;
默认值在该参数的声明中提供;
形参在成员函数中声明,并且在超函数中有一个对应的形参是可选的。
KType,代表了 Kotlin 当中的类型,它重要的成员如下:
这几个类集合了很多个API,了解每一个的作用之后再了解反射就会很简单了。