End

Kotlin 朱涛-11 注解 annotation 反射 reflect

本文地址


目录

11 | 注解与反射:进阶必备技能

注解与反射,可以提高代码的灵活性

许多著名的开源库,比如 Spring Boot、Retrofit、Gson 等,都会用到这两种技术。

注解

注解,就是对程序代码的一种补充

定义注解类

注解类使用 annotation class 定义。

例如,注解类 Deprecated 的定义如下:

@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 // 警告程度:WARNING、ERROR、HIDDEN
)

使用注解类

注解类 Deprecated 的使用如下:

@Deprecated(
    message = "字段废弃了",                  // 废弃的提示信息
    replaceWith = ReplaceWith("用什么替代"), // 应该用什么来替代废弃的部分
    level = DeprecationLevel.ERROR         // 当作是错误来看待,所以 IDE 会直接报错
)
val name: String = "bqt"

@Deprecated(message = "方法废弃了")
fun test() = "bqt"

元注解

用来修饰其他注解的注解叫做 元注解

元注解有四个:

  • @Target使用目标,指定被修饰的注解可以用在什么地方
  • @Retention保留位置,指定被修饰的注解是不是编译后可见、是不是运行时可见
  • @Repeatable是否可重复,是否允许在同一个地方多次使用,使用场景较少
  • @MustBeDocumented生成文档,指定被修饰的注解应该包含在生成的 API 文档中显示

四个元注解的定义

// 四个元注解只能用在注解类上,所以 Target 的值都是 ANNOTATION_CLASS
@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = RUNTIME)

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented

元注解 Target 的取值

public enum class AnnotationTarget {
    CLASS,            // 类、接口、object、注解类
    ANNOTATION_CLASS, // 注解类,即使用【annotation class】定义的类
    TYPE_PARAMETER,   // 泛型参数
    PROPERTY,         // 属性
    PROPERTY_GETTER,  // 属性的 getter
    PROPERTY_SETTER,  // 属性的 setter
    FIELD,            // 字段、幕后字段
    LOCAL_VARIABLE,   // 局部变量
    VALUE_PARAMETER,  // 函数参数
    CONSTRUCTOR,      // 构造器
    FUNCTION,         // 函数
    TYPE,             // 类型
    EXPRESSION,       // 表达式
    FILE,             // 文件
    TYPEALIAS         // 类型别名
}

元注解 Retention 的取值

public enum class AnnotationRetention {
    SOURCE, // 源代码。注解在编译后【不可见】,在运行时也【不可见】;注解只存在于源码中
    BINARY, // 二进制。注解在编译后【可见】,在运行时【不可见】
    RUNTIME // 运行时。注解在编译后【可见】,在运行时也【可见】
}

反射

反射可以极大地提升架构的灵活性。

Kotlin 中的反射库并没有直接集成到标准库当中,如果要用反射,需要引入这个依赖:

implementation "org.jetbrains.kotlin:kotlin-reflect"

也可以自己从 Maven 仓库中下载

反射案例:获取并修改程序信息

目的:读取任意对象中,所有的成员属性的名称和属性的值;并在满足指定条件时,修改程序信息。

import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible

data class Man(private var jobName: String)

fun readMembers(obj: Any) {
    val clazz: KClass<out Any> = obj::class        // 获取类引用
    val className: String? = clazz.simpleName      // 类的名称
    clazz.memberProperties.forEach {               // 遍历类的成员属性。类型是 KProperty1
        val fieldName: String = it.name            // 属性名称
        it.getter.isAccessible = true              // 如果为私有成员,需要将其设置为可访问
        val fieldValue: Any? = it.getter.call(obj) // 属性值
        println("${className}.${fieldName} = $fieldValue - ${it is KMutableProperty1}")

        tryModify(obj, it)
    }
}

private fun tryModify(obj: Any, kp: KProperty1<out Any, *>) {
    if (kp.name == "jobName" &&                          // 判断属性的名称
        kp is KMutableProperty1 &&                       // 判断属性是否可变的(即 var 修饰的)
        kp.setter.parameters.size == 2 &&                // 判断属性 setter 方法的参数个数
        kp.getter.returnType.classifier == String::class // 判断属性 getter 方法的返回值类型
    ) {
        kp.setter.call(obj, "码农") // 调用属性的 setter 方法,参数为:obj 自身 + 要设置的值
        println(obj.toString())
    }
}

fun main() {
    readMembers("白乾涛")
    readMembers(Man("程序员"))
}
String.length = 3 - false
Man.jobName = 程序员 - true
Man(jobName=码农)
  • 通过 obj::class 可以拿到类引用,也就是变量 obj 的实际类型,KClass 代表的是一个 Kotlin 类
  • 通过 KClass 可以拿到这个类型的所有信息,比如类的名称 simpleName,类的成员属性的集合 memberProperties
  • 类型 KMutableProperty1: Represents a var-property, operations on which take one receiver as a parameter.

反射的几个关键类

几个关键类:KClass、KCallable、KParameter、KType。

KClass

KClass 是 Kotlin 定义的一个类,和 Java 中定义的 Class 类之间没有继承关系。

val jClass: Class<String> = String::class.java // java 中定义的类所对应的 class
val kClass: KClass<String> = String::class     // kotlin 中定义的类所对应的 class

println("${kClass == jClass} - ${kClass.java == jClass}") // false  - true

KClass 代表了一个 Kotlin 的。它的重要成员有:

  • simpleName:类的名称,注意,匿名内部类的 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

KCallable

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

  • name:名称,属性和函数都有名称
  • parameters:所有的参数,类型是 List<KParameter>,指的是调用这个元素所需的所有参数
  • returnType:返回值类型,类型是 KType
  • typeParameters:所有的类型参数,类型是 List<KTypeParameter>
  • call:KCallable 对应的调用方法
  • visibility:可见性
  • isSuspend:是不是挂起函数

KParameter

KParameter 代表了 KCallable 当中的参数。它的重要成员有:

  • index:参数的位置,下标从 0 开始
  • name:参数的名称,源码当中参数的名称
  • type:参数的类型,类型是 KType
  • kind:参数的种类,对应三种情况:INSTANCE(实例)、EXTENSION_RECEIVER(扩展接受者)、VALUE(实际的参数值)

KType

KType 代表了 Kotlin 当中的类型。它的重要成员有:

  • classifier:类型对应的 Kotlin 类,即 KClass
  • arguments:类型的类型参数,其实它就是这个类型的泛型参数
  • isMarkedNullable:是否在源代码中标记为可空类型,即这个类型的后面有没有?修饰

Kotlin 中的 Class

总结

Java 中获取 Class Kotlin 中获取 Class Kotlin 中获取 KClass
通过类获取 Class String.class String::class.java String::class
通过对象获取 Class obj.getClass() obj.javaClass obj::class
原始类型的 Class int.class 123.javaClass Int::class
原始类型的 Class Int::class.javaPrimitiveType xxx.kotlin
封装类型的 Class Integer.class Integer::class.java Integer::class

普通类的 Class

Class<String> clazz1 = String.class;
Class<?> clazz2 = "bqt".getClass();
val clazz1: Class<String> = String::class.java
val clazz2: Class<String> = "bqt".javaClass

val kClazz1: KClass<out String> = String::class
val kClazz2: KClass<out String> = "bqt"::class

原始类型和封装类型的 Class

Class<Integer> intClass = int.class;          // 原始类型的 Class
Class<Integer> integerClass = Integer.class;  // 封装类型的 Class

System.out.

println(intClass);                 // int
System.out.

println(integerClass);             // class java.lang.Integer
System.out.

println(intClass ==integerClass); // false。不是同一个 Class
val intClass1: Class<Int> = 123.javaClass                           // 原始类型的 Class
val intClass2: Class<Int>? = Int::class.javaPrimitiveType           // 原始类型的 Class
val integerClass: Class<Integer> = Integer::class.java              // 封装类型的 Class

val intKClass1: KClass<Int> = Int::class                            // 原始类型的 KClass
val intKClass2: KClass<Int>? = Int::class.javaPrimitiveType?.kotlin // 原始类型的 KClass
val integerKClass: KClass<Integer> = Integer::class                 // 封装类型的 KClass

数组类型的 Class

import java.lang.reflect.Array;

Class<?> saClass = Array.newInstance(String.class, 0).getClass(); // String 数组的 Class
Class<?> iaClass = Array.newInstance(int.class, 0).getClass();    // int 数组的 Class
import java.lang.reflect.Array

val sArrayClass: Class<*> = Array.newInstance(String::class.java, 0).javaClass
val intArrayClass: Class<*> = Array.newInstance(Integer::class.java, 0).javaClass

2017-10-31

posted @   白乾涛  阅读(5381)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
历史上的今天:
2016-10-31 Builder 建造者模式
2016-10-31 Prototype 原型模式 复制 浅拷贝 clone
2016-10-31 Composite 组合模式 树 递归
2016-10-31 Decorator Wrapper 装饰模式
2016-10-31 Adapter 适配器模式
2016-10-31 Observer 观察者模式
2016-10-31 Template Method 模板方法
点击右上角即可分享
微信分享提示