反射(Reflection)是程序的自我分析能力,通过反射可以确定类中有哪些函数、构造函数以及属性。反射机制在一般的应用开发中很少使用,主要用于框架开发。
Kotlin语言本身提供了反射API,也可以通过调用Java语言反射API实现反射。通过反射机制能够动态读取一个类的信息;能够在运行时动态加载类,而不是在编译期。反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、调用函数和调用属性等。
Kotlin反射API主要来自于kotlin.reflect、kotlin.reflect.full和kotlin.reflect.jvm包。其中kotlin.reflect和kotlin.reflect.full是主要的Kotlin反射API,而kotlin.reflect.jvm包主要用于Kotlin反射和Java反射的互操作。
kotlin.reflect包是Kotlin反射核心API,它的类图如图26-1所示,它们都是接口,详细说明如下:
o KClass。表示一个具有反射功能的类。
o KParameter。表示一个具有反射功能的可传递给函数或属性的参数。
o KCallable。表示具有反射功能的可调用实体,包括属性和函数,它的直接子接口有KProperty和KFunction。
o KFunction。表示一个具有反射功能的函数。
o KProperty。表示一个具有反射功能的属性,它有很多子接口。KProperty0、KProperty1和KProperty2后面的数字表示接收者作为参数的个数。
o KMutableProperty。表示一个具有反射功能的使用var声明的属性。KMutableProperty0、KMutableProperty1和KMutableProperty2后面的数字含义同KProperty。
对类的引用是通过KClass实现的,KClass是实现反射的关键所在,KClass的一个实例表示对类的引用。在程序代码中引用类使用::运算符,引用类的示例代码如下:
//1.获得“类名::class”引用类
val clz1 = Int::class
val clz2 = Person::class
val person = Person(“Tom”)
//2.获得“对象::class”引用类
val clz3 = person::class
从上述代码可见,引用类有两种形式:类名::class和对象::class。clz1、clz2和clz3都是KClass类型,表示一个类的引用,其中clz1是KClass类型,clz2是KClass类型,clz3是KClass类型。
KClass类提供了很多函数可以获得运行时对象的相关信息,下面的程序代码展示了其中一些函数。
//代码文件:chapter26/src/com/a51work6/section2/ch26.2.kt
package com.a51work6.section2
import kotlin.reflect.full.superclasses
//声明数据类
data class Person(val name: String)
fun main(args: Array) {
//1.获得“类名::class”引用类
val clz1 =Int::class
val clz2 =Person::class
val person =Person("Tom")
//2.获得“对象::class”引用类
val clz3 =person::class
println(“clz1类名:” + clz1.simpleName) ①
println(“clz1类全名:” + clz1.qualifiedName) ②
println(“clz2是否为抽象类或接口:” + clz2.isAbstract)
println(“clz2是否为数据类:” + clz2.isData)
println(“clz2所有成员:”)
clz2.members.forEach { println(" ${it.name}") }
println(“clz3父类名称:”)
clz3.superclasses.forEach { println(" ${it.simpleName}") }
}
运行结果如下:
clz1类名:Int
clz1类全名:kotlin.Int
clz2是否为抽象类或接口:false
clz2是否为数据类:true
clz2所有成员:
name
component1
copy
equals
hashCode
toString
clz3父类名称:
Any
上述代码第①行和第②行的区别,simpleName不带包名类名,qualifiedName是带有包名的类名,即类全名。
通过反射调用函数需要KFunction实例,KFunction实例可以通过两种方式获得:一个是函数引用;另一个是通过KClass提供的API获得KFunction实例。
函数引用在14.2.2节已经接触过了,函数引用可以表示一个函数字面量,可以赋值给函数类型变量。函数引用也是使用::运算符,可以引用顶层函数也可引用类中成员函数。
通过反射调用函数的示例代码如下:
//代码文件:chapter26/src/com/a51work6/section3/ch26.3.kt
package com.a51work6.section3
import kotlin.reflect.full.createInstance
//声明Person类
class Person {
var name: String? = null
var age: Int = 0
fun setNameAndAge(name: String, age:
Int) {
this.name = name
this.age = age
}
override fun toString(): String {
return "Person [name=$name,age=$age]"
}
}
//声明相加函数
fun add(a: Int, b: Int): Int = a + b
//声明相减函数
fun sub(a: Int, b: Int) = a - b
fun calculate(opr: Char): (Int, Int) -> Int = if (opr == ‘+’) ::add else
::sub
fun main(args: Array) {
val fn1 = ::add ①
val fn2 = ::sub ②
val fn3 = calculate('+') ③
println(fn1.call(10, 5)) ④
println(fn1(10, 5)) //输出结果是15 ⑤
println(fn2(10, 5)) //输出结果是5
println(fn3(10, 5)) //输出结果是15
val clz = Person::class ⑥
val person = clz.createInstance() ⑦
clz.functions.forEach { println(it.name) } ⑧
val pfn1 =clz.functions.first() ⑨
pfn1.call(person, “Tom”, 20) ⑩
println(person)
val pfn2 = Person::setNameAndAge ⑪
pfn2(person,"Tony", 18) ⑫
println(person)
pfn2.call(person, “Ben”, 28) ⑬
println(person)
}
上述代码第①行和第②行都是引用顶层函数。代码第③行调用calculate函数,该函数的返回类型是函数类型(Int, Int) -> Int,函数引用可以作为函数字面量表示函数,所以calculate函数直接返回函数引用。这也说明的KFunction类型与函数类型是兼容的。
代码第④行是通过KFunction的call函数调用引用的函数,也可以通过第⑤行直接调用函数。
代码第⑥行是引用Person类实例,代码第⑦行是通过引用类创建实例person,createInstance函数能够创建引用类的对象。代码第⑧行functions是KClass属性,它可以获得当前类中所有函数。代码第⑨行的从functions属性中获得第一个元素,它是KFunction实例, functions集合中的第一个元素是setNameAndAge函数。
代码第⑪行是引用Person中的成员setNameAndAge函数。代码第⑩行、第⑫行和第⑬行都是调用setNameAndAge函数,注意它们的第一个参数是person,从第二参数开始才是setNameAndAge函数的参数。
通过反射调用构造函数与普通函数的调用类似。通过反射调用构造函数也是使用KFunction实例,KFunction实例可以通过两种方式获得:一个是函数引用;另一个是通过KClass提供的API获得KFunction实例。
通过反射调用构造函数的示例代码如下:
//代码文件:chapter26/src/com/a51work6/section4/Rectangle.kt
package com.a51work6.section4
class Rectangle(var width: Int, var height: Int) { ①
// 矩形面积
var area: Int = 0
init {//初始化代码块
area = width * height// 计算矩形面积
}
constructor(width: Int, height: Int,area: Int) : this(width, height) {
this.area = area
}
constructor(area: Int) : this(200,100) {//width=200 height=100
this.area = area
}
override fun toString(): String {
return"Rectangle(width=$width, height=$height, area=$area)"
}
}
//代码文件:chapter26/src/com/a51work6/section4/ch26.4.kt
package com.a51work6.section4
import kotlin.reflect.full.primaryConstructor
fun main(args: Array) {
val clz = Rectangle::class ②
clz.constructors.forEach {println(it.name) } ③
//获得主构造函数对象
val ctor1 = clz.primaryConstructor ④
val rect1 = ctor1?.call(100, 90) ⑤
println(rect1)
//获得第一个构造函数对象
val ctor2 = clz.constructors.first() ⑥
val rect2 = when(ctor2.parameters.size) { ⑦
3 -> ctor2.call(10, 9, 900)
2 -> ctor2.call(100, 90)
else -> ctor2.call(20000)
}
println(rect2)
val ctor3: (Int) -> Rectangle =::Rectangle ⑧
val rect3 = ctor3(20000) ⑨
println(rect3)
}
上述代码第①行是声明Rectangle类,它有三个构造函数,一个主构造函数,两个次构造函数。在main函数中代码第②行是声明Rectangle类引用。代码第③行中的constructors属性是获得Rectangle中所有的构造函数。代码第④行primaryConstructor属性是获得主构造函数,代码第⑤行是通过call函数调用主构造函数。
代码第⑥行是从构造函数集合中取出第一个元素,由于不知道第一个构造函数有几个参数,所以通过代码第⑦行是判断参数的个数,根据个数选择调用哪一个构造函数,parameters属性返回构造函数参数集合。
代码第⑧行::Rectangle是构造函数引用,由于Rectangle类有三个构造函数,编译器不能确定::Rectangle是引用哪一个构造函数,所以需要指定ctor3变量的类型,ctor3变量应该声明为KFunction类型,但是需要提供泛型类型,比较麻烦,本例中ctor3变量声明为函数类型(Int) -> Rectangle,函数类型与KFunction是兼容的。但需要注意的是在调用使用函数类型声明的变量不能使用call函数调用,见代码第⑨行直接调用ctor3。
通过反射调用属性需要KProperty实例。获得KProperty实例可以通过两种方式获得:一个是属性引用;另一个是通过KClass提供的API获得KProperty实例。
通过反射调用属性的示例代码如下:
//代码文件:chapter26/src/com/a51work6/section5/ch26.5.kt
package com.a51work6.section5
import kotlin.reflect.full.memberProperties
//声明Person类
class Person(var name: String, var age: Int) { ①
fun setNameAndAge(name: String, age: Int) {
this.name =name
this.age =age
}
override fun toString(): String {
return "Person [name=$name, age=$age]"
}
}
//顶层属性
val count = 100 ②
fun main(args: Array) {
val clz =Person::class
clz.memberProperties.forEach { println(it.name) } ③
//Person构造函数引用
val personCtor= ::Person
//创建Person实例
val person =personCtor.call(“Tom”, 20)
//获得第一个属性
val prop1 =clz.memberProperties.first() ④
println(prop1.get(person))
//引用顶层属性
val propCount =::count ⑤
//读取count属性
println(propCount.get()) ⑥
//引用成员属性name
val propName =Person::name ⑦
//写入成员属性name
propName.set(person, “Tony”) ⑧
//读取成员属性name
println(propName.get(person)) ⑨
//引用成员属性age
val propAge =Person::age
//写入成员属性age
propAge.set(person, 20)
//读取成员属性age
println(propAge.get(person))
}
上述代码第①行声明Person类,它有一个主构造函数。代码第②行声明顶层属性count。代码第③行中的memberProperties属性可以获得Person类所有的属性集合。代码第④行获得属性集合中的一个元素。
代码第⑤行是获得顶层属性count引用,它是一个只读属性,获得属性是通过get函数,见代码第⑥行,由于是顶层属性,所以get函数没有参数。代码第⑦行是获得成员属性name引用,它是可读写属性。代码第⑧行set函数是写入name属性,set函数第一个参数是person实例,第二个参数是要写入的数值。代码第⑨行get函数是读取name属性,参数是person实例。
本章介绍了Kotlin的反射机制,读者应该清楚什么时候使用反射。本章还详细介绍了通过反射机制创建对象、调用函数、调用构造函数和调用属性,读者需要了解这些API的使用。