文章

Kotlin反射

Kotlin反射

Kotlin 反射基础

Kotlin 反射和 Java 反射

Kotlin 反射类图:
image.png
Java 反射类图:
image.png
Kotlin 和 Java 的对比:

  • Kotlin 的反射类都是基于 KAnnotatedElement, 而 Java 的反射类都是基于 AnnotateElement;
  • Kotlin 的 KCallable 和 Java 的 AccessiableObject 都是可用元素;
  • Kotlin 的 KProperty 和 Java 的 Field 不太相同。 Kotlin 的 KProperty 通常指相应的 Getter 和 Setter 整体作为一个 KProperty(不存在字段的概念),而 Java 的 Field 通常仅仅指字段本身。

引入 reflect 库

Kotlin 的反射需要集成 org.jetbrains.kotlin:kotlin-reflect 仓库,版本保持与 kotlin 一致。

1
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

常用 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 1.通过类::class的方式获取Kclass实例
val clazz1: KClass<*> = XXX::class
// 2. 通过实例.javaClass.kotlin获取Kclass实例
var xxx = XXX()
val clazz2 = xxx.javaClass.kotlin

// 构造函数Constructor
// 返回这个类的所有构造器
public val constructors: Collection<KFunction<T>>

// 成员变量和成员函数
//返回类可访问的所有函数和属性,包括继承自基类的,但是不包括构造器
override val members: Collection<KCallable<*>>
//返回类声明的所有函数
val KClass<*>.declaredFunctions: Collection<KFunction<*>>
//返回类的扩展函数
val KClass<*>.declaredMemberExtensionFunctions: Collection<KFunction<*>>
//返回类的扩展属性
val <T : Any> KClass<T>.declaredMemberExtensionProperties: Collection<KProperty2<T, *, *>>
//返回类自身声明的成员函数
val KClass<*>.declaredMemberFunctions: Collection<KFunction<*>>
//返回类自身声明的成员变量(属性)
val <T : Any> KClass<T>.declaredMemberProperties: Collection<KProperty1<T, *>>

// 类相关信息
//1.返回类的名字
public val simpleName: String?
//2.返回类的全包名
public val qualifiedName: String?
//3.如果这个类声明为object,则返回其实例,否则返回null
public val objectInstance: T?
//4.返回类的可见性
@SinceKotlin("1.1")
public val visibility: KVisibility?
//5.判断类是否为final类(在Kotlin中,类默认是final的,除非这个类声明为open或者abstract)
@SinceKotlin("1.1")
public val isFinal: Boolean
//6.判断类是否是open的(abstract类也是open的),表示这个类可以被继承
@SinceKotlin("1.1")
public val isOpen: Boolean
//7.判断类是否为抽象类
@SinceKotlin("1.1")
public val isAbstract: Boolean
//8.判断类是否为密封类,密封类:用sealed修饰,其子类只能在其内部定义
@SinceKotlin("1.1")
public val isSealed: Boolean
//9.判断类是否为data类
@SinceKotlin("1.1")
public val isData: Boolean
//10.判断类是否为成员类
@SinceKotlin("1.1")
public val isInner: Boolean
//11.判断类是否为companion object
@SinceKotlin("1.1")
public val isCompanion: Boolean 
//12.返回类中定义的其他类,包括内部类(inner class声明的)和嵌套类(class声明的)
public val nestedClasses: Collection<KClass<*>>
//13.判断一个对象是否为此类的实例
@SinceKotlin("1.1")
public fun isInstance(value: Any?): Boolean
//14.返回这个类的泛型列表
@SinceKotlin("1.1")
public val typeParameters: List<KTypeParameter>
//15.类其直接基类的列表
@SinceKotlin("1.1")
public val supertypes: List<KType>
//16.返回类所有的基类
val KClass<*>.allSuperclasses: Collection<KClass<*>>
//17.返回类的伴生对象companionObject
val KClass<*>.companionObject: KClass<*>?

类引用

Kotlin 中的类引用用 KClass 表示,引用的是 KClass 对象,Java 的类引用是 java.lang.Class 对象,二者不一样

1
2
3
4
5
6
7
8
9
10
11
// 已知Kotlin类,获取KClass
val kClass = TestObj::class // KClass<TestObj>

// 已知Kotlin对象,获取KClass和Class
val testObj = TestObj("hacket")
val kClass1 = testObj::class  // KClass<TestObj>
val clazz2 = testObj.javaClass // Class<TestObj>

// 通过KClass获取对应的Java类引用:调用KClass对象的java属性
val clazz1 = testObj::class.java // Class<TestObj>
val clazz = TestObj::class.java // Class<TestObj>

反射创建实例

方式一

获取到 KClass 对象后,可以调用其 createInstance() 方法创建该类的实例,该方法_ 总是调用该列无参构造器创建的实例 _,因此使用该方法的前提就是必须提供无参构造器或构造器都是默认参数

方式二

通过获取到的 KClass 对象的,借助其 constructors 获取到所有构造器,再利用条件判断返回目标构造器,最后通过目标构造器对象的 call() 创建出对应的实例

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
val itemClz = Item::class;
val instance = itemClz.createInstance()
println("1. createInstance()调用无参构造器创建实例")
println("instance.name=${instance.name},instance.price=${instance.price}")

println("2. 调用有参构造器创建实例")
itemClz.constructors.forEach {
    if (it.parameters.size == 2) {
        val instance = it.call("酸奶", 9.0)
        println("instance.name=${instance.name},instance.price=${instance.price}")
    }
}
class Item(var name: String) {
    var price = 0.0
    constructor(name: String, price: Double) : this(name) {
        this.price = price
    }
    constructor() : this("未知商品") {
    }
}

输出:

1
2
3
4
1. createInstance()调用无参构造器创建实例
instance.name=未知商品,instance.price=0.0
2. 调用有参构造器创建实例
instance.name=酸奶,instance.price=9.0

构造器引用

构造器本质也是函数,即一个返回值为当前类实例的函数,可以将构造器引用当成函数使用;Kotlin 允许通过 :: 操作符并添加类名来引用_ 主构造器 _::类名

1
2
3
4
5
6
7
8
9
10
11
12
private fun testConstructor() {
    val fruit = Fruit()
    fruit.test(::Fruit)
}


class Fruit(var name: String = "未知") {
    fun test(apple: (String) -> Fruit) {
        val apple1: Fruit = apple("苹果")
        println("Fruit实例name属性:${apple1.name}")
    }
}

输出:

1
Fruit实例name属性:苹果

和函数作为方法形参也一样,当把函数赋值给变量时,通过 “:: 函数名 “ 既可,构造器大同小异,是通过 “::类名” 将构造器赋值给变量;

需要说明的是:如果要调用 Kotlin 构造器引用对应的 Java 构造器对象,则可通过 KFunction 的扩展属性 javaConstructor 来实现;

1
2
// 调用构造器引用的javaCostructor属性,需要导入Kotlin.reflect.jvm包,这些扩展属性属于与Java反射互相操作的部分
::Fruit.javaConstructor

调用方法 KFunction

所有构造器和方法都是 KFunction 的实例,调用他们可以通过其 call() 方法;

  1. 获取方法对象(KFunction 的实例)
  2. 调用方法对象的 call()

方法是面向对象的,必须有主谓宾如(” 猪八戒.吃(西瓜)”),因此在转函数时其形参都是会比对应方法多一个参数即:主语(方法调用者),就变了 “ 吃(猪八戒,西瓜)”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun testFunction() {
    val kclz = Foo::class
    val instance = kclz.createInstance()
    kclz.declaredMemberFunctions.forEach {
        println("Foo所有成员方法:$it \n parameters[0]=${it.parameters[0]}\n parameters[1]=${it.parameters[1]}")
        if (it.parameters.size == 2) {
            it.call(instance, "Hello Kotlin")
        }
    }
}

class Foo {
    fun test(msg: String) {
        println("执行带参方法,mag=$msg")
    }
}

输出:

1
2
3
4
Foo所有成员方法:fun me.hacket.reflect.Foo.test(kotlin.String): kotlin.Unit 
 parameters[0]=instance parameter of fun me.hacket.reflect.Foo.test(kotlin.String): kotlin.Unit
 parameters[1]=parameter #1 msg of fun me.hacket.reflect.Foo.test(kotlin.String): kotlin.Unit
执行带参方法,mag=Hello Kotlin

反射调用 test 方法,除了正常的参数 msg 外,第 1 个位置的隐藏参数为 Foo 对象实例

函数引用

我们多次说过,函数也是一等公民,将函数赋值给引用是 ::函数名

1
2
3
4
5
6
7
8
9
10
fun testFunctionRef() {
    // 当我们将函数赋值给未显示指定变量类型的引用,就会报错
    // var f = ::isSmall
    // 这儿就必须显示声明变量类型,正确写法
    val f: (String) -> Boolean = ::isSmall
}

fun isSmall(i: String) = i.length < 5

fun isSmall(i: Int) = i < 5

如果将成员方法扩展方法赋值给引用,需要使用限定:类名:: 成员/扩展方法 。方法类型也不是函数的 (形参类型)->返回值类型 类型,同样需要使用限定即类名.(形参类型)->返回值类型

总结:

  1. _ 函数赋值 _ 给引用变量是 ::函数名,该变量类型为函数类型是 (形参类型)->返回值类型
  2. _ 类方法、扩展方法赋值 _ 给引用变量时是 类名::方法名,该变量类型是 类名.(形参类型)->返回值类型
  3. 如果要获取 Kotlin 函数引用的 java 方法对象,可以通过 KFunction 的扩展属性 javaMethod 实现:::方法名.javaMeethod

访问属性值

获取到 KClass 对象后,通过该对象来获取该类包含的属性:

  1. KProperty:代表通用的属性,他是 KCallable 的子接口
  2. KMutableProperty:代表通用的读写属性,他是 KProperty 的子接口
  3. KProperty0:代表无需调用者的属性(静态属性),他是 KPropety 的子接口
  4. KMutableProperty0:代表无需调用者的读写属性(静态读写属性),他是 Kproperty 的子接口
  5. Kproperty1: 代表需要 1 个调用者的属性(成员属性),他是 KProperty 的子接口
  6. KMutableProperty1: 代表一个调用者的读写属性(成员读写属性)他是 KProperty1 的子接口;
  7. Kproperty2: 代表需要两个调用者的属性(扩展属性),他是 KProperty 的子接口
  8. KMutableProperty2: 代表需要 2 个调用者的读写属性(扩展读写属性),他是 KProperty2 的子接口

从命名字面意思就可以知道:Mutable:表示读写属性;PropertyN 中的数字表示:0:表示无需调用者即静态属性,1:成员属性,2:表示扩展属性

当获取到 KProperty 对象后,可以调用 get() 方法获取该属性的值;如果是读写属性,需要设置值,则需要获取代表读写属性 KMutableProperty 对象,调用 set()

1
2
3
4
5
6
7
8
9
10
11
12
fun testField() {
    val userKClass = User::class
    val members = userKClass.members
    println("User members.size=${members.size}\nmembers=${members}")
    val m0 = members.iterator().next()
    println("members[0] = $m0")
}

data class User(
    val name: String,
    val age: Int
)

输出:

1
2
3
User members.size=8
members=[val me.hacket.reflect.User.age: kotlin.Int, val me.hacket.reflect.User.name: kotlin.String, fun me.hacket.reflect.User.component1(): kotlin.String, fun me.hacket.reflect.User.component2(): kotlin.Int, fun me.hacket.reflect.User.copy(kotlin.String, kotlin.Int): me.hacket.reflect.User, fun me.hacket.reflect.User.equals(kotlin.Any?): kotlin.Boolean, fun me.hacket.reflect.User.hashCode(): kotlin.Int, fun me.hacket.reflect.User.toString(): kotlin.String]
val me.hacket.reflect.User.age: kotlin.Int

属性引用

Kotlin 同样提供了 :: 符号加属性名的形式获取属性引用;获取的属性引用属于前面的 KProperty 及其子类接口实例;获取到的读写属性引用,可以调用 set()、get() 修改、获取属性值,只读属性可以通过 get() 方法获取属性值

  • 包级属性:::属性名
  • 类成员属性:类名::属性名
  • 在 kotlin.reflect.jvm 包下提供了 Kotlin 属性与 java 反射互操作的扩展属性 (kotlin 属性对应 java 中的字段、getter、setter 方法三种成员),Kproperty 类包含三个扩展属性:
    1. javaField: 获取该属性的幕后字段(如果该属性有幕后字段),该属性返回 java.lang.reflect.Field 对象
    2. javaGetter: 获取该属性的 getter 方法,该属性返回 java.lang.reflect.Method 对象
    3. javaSetter: 获取该属性的 setter 方法(如果该属性是读写属性),该属性返回 java.lang.reflect.Method 对象

绑定的方法与属性引用

前面我们在获取类中静态属性、实例属性、静态方法、实例方法引用时,都是直接通过类名限定(类名:: 方法名、属性名),在获取到方法引用、属性引用时,无论是调用方法或者设置属性值时第一个参数**_ 必须传入该方法、属性的调用者即该方法、属性所属对象实例 **(参数个数=等于形参个数 + 方法调用者)

Kotlin1.1 支持绑定的方法与属性引用,方法、属性引用不是通过类(名)获取(如:类名:: 方法名),而是通过对象获取(如:” 对象名:: 方法/属性名 “),这样在调用该方法是将不用在传入该方法的调用者了(其实就是获取实例方法、实例属性引用);

1
2
3
4
5
val str = "kotlin"
val f: (CharSequence, Boolean) -> Boolean = str::endsWith

// 无须再传入方法调用者
f("lin", true)

Kotlin 反射案例

  • Kotlin 中反射修改 final 的值,用 Java 反射更简单?

Ref

详细,值得一看

链接
https://blog.csdn.net/yxhuang2008/article/details/119294187
https://aisia.moe/2018/01/07/kotlin-jiqiao/
https://blog.csdn.net/qq_31339141/article/details/108411934?ydreferer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS5oay8%3D
反射调用Kotlin类里的Companion函数

本文由作者按照 CC BY 4.0 进行授权