Kotlin注解与反射的定义及创建使用详解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Kotlin注解与反射的定义及创建使用详解

无糖可乐爱好者   2022-12-08 我要评论

1.注解

1.定义

注解是将元数据附加到代码的地方。从字面意思理解它就是对知识点的补充,一种描述。在Java中最常见的注解就是@Override或者就是Retrofit中的@GET@POST等。

2.注解的创建

创建时用annotation修饰符进行声明,如

annotation class GET()

这样就创建好了一个注解,但是这里想要完全使用还要添加一些属性:

  • @Target:指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式);
  • @Retention:指定该注解是否存储在编译后的class文件中以及它在运行时能否通过反射可见,默认为true;
  • @Repeatable:允许在单个元素上多次使用相同的该注解;
  • @MustBeDocumented:指定该注解是公有API的一部分,并且应该包含在生成的API文档中显示的类或方法的签名中,一般用于SDK文档中。

这里重点要注意的是 @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
}

3.注解的使用

@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内部还传递了几个参数:

  • message:对废弃内容的提示信息
  • repleaceWith:表示用什么内容来替代被废弃的内容。需要注意的是后面的ReplaceWith也是一个注解,也就是说Kotlin中注解中是可以添加注解的,只不过添加时不可以用@
  • level:警告程度,有WARNINGERRORHIDDEN

注解中允许的参数有:

  • 对应于 Java 原生类型的类型(Int、 Long等)
  • 字符串
  • 类(Foo::class)
  • 枚举
  • 其他注解
  • 上面已列类型的数组

注解参数不能有可空类型,因为 JVM 不支持将 null 作为注解属性的值存储。

2.反射

1.定义

反射是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

反射在业务开发中用的较少,主要是在架构设计中,可以极大地提升架构的灵活性。

Kotlin的反射具备三个特点:

  • 感知程序的状态,包含程序的运行状态以及源代码结构;
  • 修改程序的状态;
  • 根据程序的状态,调整自身的决策行为。

2.反射的应用

首先要加入一个依赖才可以使用反射

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 = 用脚走

看到这个是不是一脸懵?这是啥鬼东西。我们对上面的代码进行分析就明白了:

  • obj::class: 这是类引用,是Kotlin的反射语法,通过这样的语法可以获取变量的类型信息并且可以拿到这个变量的类型KClass,也就是我们的PersonAnimal
  • memberProperties: 通过前面的obj::class拿到了具体的类型,那么也就拿到了这个这个类型的所有信息,比如说simpleNameconstructors,而memberProperties就是获取类的成员属性,然后通过foreach遍历出来就好了。
  • it:KProperty1: 这里的KProperty1是KClass的子类,通过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 的类,下面是它的重要成员:

  • simpleName: 获取类名称,如果是匿名内部类获取的值为null;
  • qualifiedName: 完整的类名。用【.】分隔
  • members: 可访问的所有函数和属性,类型为Collection<KCallable<*>>
  • constructors: 获取所有构造函数,类型为Collection<KFunction<T>>
  • nestedClasses: 获取声明的所有类包含内部嵌套类和嵌套静态类,类型为Collection<KClass<*>>
  • objectInstance: 对象声明的实例如果这个类不是对象声明则返回null;
  • typeParameters: 获取参数类型列表,但不包括外部类的参数类型,类型为List<KTypeParameter>
  • supertypes: 该类的直接超类型列表,按它们在源代码中列出的顺序排列,类型为List<KType>
  • sealedSubclasses: 如果是密封类则直接获取子类的列表否则为空,类型为List<KClass<out T>>
  • visibility: 类的可见性,如果可见性不能在Kotlin中表示则为null;
  • isFinal: 返回该类是否是final类型,类型为Boolean,如果是则为true;
  • isOpen: 返回该类是否是open修饰的,类型为Boolean,如果是则为true;
  • isAbstract: 返回该类是否是abstract的,类型为Boolean,如果是则为true;
  • isSealed: 返回该类是否是密封类,类型为Boolean,如果是则为true;
  • isData: 返回该类是否是数据类,类型为Boolean,如果是则为true;
  • isInner: 返回该类是否是内部类,类型为Boolean,如果是则为true;
  • isCompanion: 返回该类是否是伴生对象,类型为Boolean,如果是则为true;
  • isFun: 返回该类是否是函数式接口,类型为Boolean,如果是则为true;
  • isValue: 返回该类是否是Value Class,类型为Boolean,如果是则为true。

KCallable 代表了 Kotlin 当中的所有可调用的元素,比如函数、属性、甚至是构造函数。下面是 KCallable 的重要成员:

  • name: 获取可调用的声明的名称,如果可调用对象没有名称则创建一个特殊的名称,没有名称包括:

构造函数名称为"";

属性访问器:一个名为foo的属性getter将会有名称,同理setter将会有名称

  • parameters 获取所有可调用的参数,如果需要this实例或者扩展接收方参数那么他们通过List返回,返回类型为List<KParameter>
  • returnType 获取返回值的类型,返回类型为KType
  • typeParameters 获取可调用参数的类型以列表返回,返回类型为List<KTypeParameter>
  • visibility 元素的可见性,如果可见性不能在Kotlin中表示则为null;
  • isFinal 返回该元素是否是final修饰的,类型为Boolean,如果是则为true;
  • isOpen 返回该元素是否是open修饰的,类型为Boolean,如果是则为true;
  • isAbstract 返回该元素是否是abstract修饰的,类型为Boolean,如果是则为true;
  • isSuspend 返回该元素是否是挂起函数,类型为Boolean,如果是则为true。

KParameter,代表了KCallable当中的参数,它的重要成员如下:

  • index: 参数在参数列表中的索引,从0开始;
  • name: 参数声明的名称,如果没有名称或者名称在运行时不可用则返回null;
  • type: 参数类型,对于可变参数参数类型是数组而不是单个元素,返回类型为KType
  • kind: 参数的种类

INSTANCE: 对象的实例;

EXTENSION_RECEIVER: 扩展接收者;

VALUE: 具体的值;

  • isOptional: 如果此参数是可选的,则为true,当通过KCallable进行调用时可以省略此参数。callBy,否则为false。参数可选的情况如下:

默认值在该参数的声明中提供;

形参在成员函数中声明,并且在超函数中有一个对应的形参是可选的。

  • isVararg: 如果参数为可变长度则返回true。

KType,代表了 Kotlin 当中的类型,它重要的成员如下:

  • classifier: 类型对应Kotlin类,即KClass,如果不能在Kotlin中表示则返回null;
  • arguments: 是该类型中的分类器的参数传递的类型参数,就是泛型。
  • isMarkedNullable: 是否被标记为可空类型,就是后面有没有【?】。

这几个类集合了很多个API,了解每一个的作用之后再了解反射就会很简单了。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们