一、注解与反射
注解与反射的意义:提高代码的灵活性。只有深刻理解了注解和反射,我们才可能理解那些著名开源库的设计思路,也才可能读懂这些世界顶级开发者的代码。
二、注解
1、认识注解
Kotlin 当中的注解,其实就是“程序代码的一种补充”。比如我们在Test包中去写测试代码
添加@TEST注解和不添加@Test注解的测试代码:
告诉系统上面的是测试代码,于是系统会在左边添加绿色小三角,可以直接点击运行。
2、注解的定义
如果我们想定义一个 @Deprecated 注解,应该怎么做呢?其实非常简单,总体结构
和定义一个普通的 Kotlin 类差不多,只是多了一些额外的东西。
import kotlin.annotation.AnnotationTarget.*
@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
)
从上面的代码里,我们可以看到,@Deprecated 这个注解的定义上面,还有其他的注解
@Target、@MustBeDocumented。这样的注解,我们叫做元注解,即它本身是注解的同时,
还可以用来修饰其他注解。
Kotlin 常见的元注解有四个:
@Target,这个注解是指定了被修饰的注解都可以用在什么地方,也就是目标;@Retention,这个注解是指定了被修饰的注解是不是编译后可见、是不是运行时可见,也 就是保留位置;@Repeatable,这个注解是允许我们在同一个地方,多次使用相同的被修饰的注解,使用 场景比较少;@MustBeDocumented,指定被修饰的注解应该包含在生成的 API 文档中显示,这个注解 一般用于 SDK 当中。
这里,你需要注意的是 Target 和 Retention 的取值:
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
}
public enum class AnnotationRetention {
//注解只存在于源代码,编译后不可见
SOURCE,
//注解编译后可见,运行时不可见
BINARY,
//编译后可见,运行时可见
RUNTIME
}
在这段代码的注释当中,我详细解释了 Target 和 Retention 的取值,以及它们各自代表的意
义。现在我们就可以回过头,来看看我们定义的@Deprecated到底是什么含义。
通过 @Target 的取值,我们可以看到,@Deprecated 只能作用于这些地方:类、 函数、 属
性、注解类、构造器、属性 getter、属性 setter、类型别名。此外,@Deprecated 这个类当中
还包含了几个成员:message 代表了废弃的提示信息;replaceWith 代表了应该用什么来替代
废弃的部分;level 代表警告的程度,分别是 WARNING、ERROR、HIDDEN。
OK,现在我们已经知道如何定义注解了,接下来看看如何用它。我们仍然以 @Deprecated
注解为例。
3、注解的使用
假设现在我们要开发一个计算器,第一个版本的 Calculator 代码出现了问题,然后这个问题在CalculatorV3 当中修复了。这时候,我们希望所有的调用方都将 Calculator 改为CalculatorV3。这种情况,@Deprecated这个注解就恰好符合我们的需求。看如下代码:
@Deprecated(message = "Use CalculatorV3 instead", replaceWith = ReplaceWith("CalculatorV3"),level = DeprecationLevel.ERROR)
class Calculator {
//错误逻辑
fun add(a: Int, b: Int): Int = a - b
}
class CalculatorV3 {
//正确逻辑
fun add(a: Int, b: Int): Int = a + b
}
fun main(){
val calculator = Calculator() //报红
}
因为我们没有写自定义注解的实现,其实这里报红是官方注解提示的。并不是我们上面的自定义注解。
还有,由于我们使用的 level 是 DeprecationLevel.ERROR,所以 IDE 会直接报错。而如果我们使用的是DeprecationLevel.WARNING,IDE 就只会提示一个警告,而不是错误了。
好了,到这里,我们就了解了注解要如何定义和使用。其实啊,只要我们真正理解了 Kotlin 的
注解到底是什么东西,前面的这些注解的语法是很容易就能记住的。不过,Kotlin 注解在使用
的时候,还有一个细节需要注意,那就是注解的精确使用目标。
4、注解的精确使用目标
我们看一个具体的例子,比如 Dagger 当中的 @Inject 注解:
object Singleton {
// 1
// ↓
@set:Inject
lateinit var person: Person
// ↑
// 2
}
注释1:如果去掉 set 这个标记,直接使用 @Inject 这个注解,我们的程序将无法正常工
作。这是因为 Kotlin 当中的一个 var 修饰的属性,它会有多种含义:这个属性背后的字段
(field)、这个属性对应的 setter、还有这个属性对应的 getter,在没有明确标注出来要用
哪个的时候,@Inject 根本不知道该如何决定。因此,这里的“@set:Inject”的作用,就是明
确标记出注解的精确使用目标(Use-site targets)。
注释2:如果没有 lateinit 这个关键字,person 这个属性是必须要有初始值的,要么直接赋
值,要么在构造函数当中被赋值。因为如果它不被初始化,person 这个属性将为空,而这
个就和它的类型“不可为空的 Person 类型”冲突了。而加上 lateinit 修饰的属性,即使它是不
可为空的,编译器也会允许它无初始值。但当我们需要依赖注入的时候,常常需要与lateinit 结合使用。
实际上,注解的精确使用目标,一般是和注解一起使用的,在上面的例子当中,set 就是和@Inject 一起使用的。而除了 set 以外,Kotlin 当中还有其他的使用目标:
file,作用于文件;property,作用于属性;field,作用于字段;get,作用于属性 getter;set,作用于属性 setter;receiver,作用于扩展的接受者参数;param,作用于构造函数参数;setparam,作用于函数参数;delegate,作用于委托字段。
三、反射
1、准备工作
如果想要使用反射,需要填入依赖:
implementation "org.jetbrains.kotlin:kotlin-reflect"
我们需要反射下面这个类:
class Person {
var userName: String = "unKnow"
var age: Int = 0
}
2、读取成员变量
fun readMembers(obj: Any) {
obj::class.memberProperties.forEach {
Log.d("TAG", "${obj::class.simpleName}.${it.name}=${it.getter.call(obj)}")
}
}
//调用
readMembers(Person())
//打印
TAG: Person.age=0
TAG: Person.userName=unKnow
obj::class,这是 Kotlin 反射的语法,我们叫做类引用,通过这样的语法,我们就可以读取一个变量的“类型信息”,并且就能拿到这个变量的类型,它的类型是 KClass。memberProperties是KClasses.kt中KClass的扩展方法:
/**
* Returns non-extension properties declared in this class and all of its superclasses.
*/
@SinceKotlin("1.1")
val <T : Any> KClass<T>.memberProperties: Collection<KProperty1<T, *>>
get() = (this as KClassImpl<T>).data().allNonStaticMembers.filter { it.isNotExtension && it is KProperty1<*, *> } as Collection<KProperty1<T, *>>
另外通过调用属性的 getter.call(),就可以拿到 obj属性的值了,于是有了打印的结果。
3、修改成员变量
假如我们想修改Person类中userName的默认值应该怎么办? 看下面的代码:
/**
* @param memberName 成员属性的名称
* @param newValue 成员属性的新的值
*/
fun changeStringMembers(obj: Any, memberName: String, newValue: String) {
obj::class.memberProperties.forEach {
if (it.name == memberName && //属性的名称
it is KMutableProperty1 && //判断属性是否可变
it.setter.parameters.size == 2 && //这里 setter 的参数个数应该是 2,第一个参数是 obj 自身,第二个是实际的值
it.getter.returnType.classifier == String::class //根据属性的 getter 的返回值类型 returnType,来判断属性的类型是不是 String 类型
) {
it.setter.call(obj, newValue) //修改属性的值
}
Log.d("TAG", "${obj::class.simpleName}.${it.name}=${it.getter.call(obj)}")
}
}
//调用
changeStringMembers(Person(),"userName","张三")
//打印
TAG: Person.age=0
TAG: Person.userName=张三
4、反射的关键成员
Kotlin 反射的几个关键的反射 Api 和类:KClass、KCallable、KParameter、KType。现在,我们来进一步看看它们的关键成员。
(1)KClass 代表了一个 Kotlin 的类,下面是它的重要成员:
| 重要成员 | 说明 |
|---|---|
| simpleName | 类的名称,对于匿名内部类,则为 null |
| qualifiedName | 完整的类名 |
| members | 所有成员属性和方法,类型是Collection<KCallable<*>> |
| constructors | 类的所有构造函数,类型是Collection<KFunction<T>>> |
| nestedClasses | 类的所有嵌套类,类型是Collection<KClass<*>> |
| visibility | 类的可见性,类型是KVisibility?,分别是这几种情况,PUBLIC、PROTECTED、INTERNAL、PRIVATE |
| isFinal | 是不是 final |
| isOpen | 是不是 open |
| isAbstract | 是不是抽象的 |
| isSealed | 是不是密封的 |
| isData | 是不是数据类 |
| isInner | 是不是内部类 |
| isCompanion | 是不是伴生对象 |
| isFun | 是不是函数式接口 |
| isValue | 是不是 Value Class |
(2)KCallable 代表了 Kotlin 当中的所有可调用的元素,比如函数、属性、甚至是构造函数。下面是 KCallable 的重要成员:
| 重要成员 | 说明 |
|---|---|
| name | 名称,这个很好理解,属性和函数都有名称 |
| parameters | 所有的参数,类型是List<KParameter>,指的是调用这个元素所需的所有参数 |
| returnType | 返回值类型,类型是 KType |
| typeParameters | 所有的类型参数 (比如泛型),类型是List<KTypeParameter> |
| call() | KCallable 对应的调用方法,在前面的例子中,我们就调用过 setter、getter 的 call()方法 |
| visibility | 可见性 |
| isSuspend | 是不是挂起函数 |
(3)KParameter,代表了KCallable当中的参数,它的重要成员如下:
| 重要成员 | 说明 |
|---|---|
| index | 参数的位置,下标从 0 开始 |
| name | 参数的名称,源码当中参数的名称 |
| type | 参数的类型,类型是 KType |
| kind | 参数的种类,对应三种情况:INSTANCE 是对象实例、EXTENSION_RECEIVER 是扩展接受者、VALUE 是实际的参数值。 |
(4)KType,代表了 Kotlin 当中的类型,它重要的成员如下:
| 重要成员 | 说明 |
|---|---|
| classifier | 类型对应的 Kotlin 类,即 KClass,我们前面的例子中,就是用的 classifier ==String::class 来判断它是不是 String 类型的; |
| arguments | 类型的类型参数,看起来好像有点绕,其实它就是这个类型的泛型参数 |
| isMarkedNullable | 是否在源代码中标记为可空类型,即这个类型的后面有没有“?”修饰 |