前序
当在Kotlin中使用反射时,你会和两种不同的反射API打交道。
- 标准的Java反射,定义在包
java.lang.reflect中。因为Kotlin类会被编译成普通的Java字节码,Java反射API可以完美地支持它们。 - Kotlin反射API,定义在包
kotlin.reflect中。通过Kotlin反射API你可以访问那些在Java世界里不存在的概念,诸如属性和可空类型。Kotlin反射API并不是Java反射API的替代方案。同时,Kotlin反射API没有仅局限于Kotlin类,可以使用同样的API访问用任何JVM语言写的类。
类引用
最基本的反射功能是获取 Kotlin 类的运行时引用。
val daqiKotlinClass = daqiKotlin::class
Kotlin的类引用实际是KClass 类型的值。如果想获取Java类引用,可以在KClass 类实例上使用.java属性获取。
对于Java类引用,可以使用.kotlin将其转换为Kotlin类引用
val daqiKotlinJavaClass = daqiKotlin::class.java
val daqiKotlinaClass = daqiKotlinJavaClass.kotlin
| 属性或函数 | 含义 |
|---|---|
| memberProperties | 此类及其所有超类中声明的非扩展属性 |
| memberFunctions | 此类及其所有超类中声明的非扩展非静态函数 |
| members | 此类及其所有超类中声明的非扩展非静态函数和属性,不包括构造函数 |
| constructors | 全部构造函数 |
| isFinal | 是否是final类 |
| isOpen | 是否是open类 |
| isAbstract | 是否是抽象类 |
| isSealed | 是否是密封类 |
| isData | 是否是数据类 |
| isInner | 是否是内部类 |
| isCompanion | 是否是伴生对象 |
引入Kotlin反射
在 Java 平台上,使用反射功能所需的运行时组件作为单独的 JAR 文件(kotlin-reflect.jar)分发。这样做是为了减少不使用反射功能的应用程序所需的运行时库的大小。如果你需要使用反射,请确保该 .jar文件添加到项目的 classpath 中。
在Gradle中添加Kotlin反射jar包(具体可看这里):
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
//添加Kotlin反射jar包
implementation "org.jetbrains.kotlin:kotlin-reflect"
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
}
KCallable
在Kotlin的类引用KClass里,属性和函数都是存放在KCallable集合members中,而构造函数存储在KFunction集合constructors中。KFunction的超接口是KCallable,也就是说KCallable是函数(包括构造函数)和属性的超接口。
KCallable中声明了call方法,允许你调用对应的函数或者对应属性的getter:
public actual interface KCallable<out R> : KAnnotatedElement {
public fun call(vararg args: Any?): R
.....
}
函数KFunction
学习lambda的时候,学习过方法引用,使用::操作符引用方法,同时会返回一个KFunction对象。
对于KFunction对象,可以使用KCallable#call方法来调用被引用的函数。但::daqi同时也是类型KFunctionN的对象(N代表具体的参数数量,与lambda的Function类似),可以通过invoke方法调用该引用函数。(由于该invoke方法是一个openator方法,按照约定可以使用()替代调用invoke方法)
fun main(){
val func = ::daqi
func.invoke("")
func.call("")
}
fun daqi(name:String){
}
KCallable#call与KFunctionN#invoke的区别在于,KFunctionN#invoke的形参类型和返回值类型是可以确定的,编译器可以做检查。如果实参类型和数量与invoke不一致,编译器不会编译通过。而KCallable#call对所有类型都有效,也意味着需要开发者进行匹配对应的实参类型和数量,否则运行时会得到异常。
每一个
KFunctionN类型都继承了KFunction并加上一个额外的拥有数量刚好参数的invoke。例如:KFunction2声明了openator fun invoke(p1:P1,p2:P2):R。KFunctionN类型属于合成的编译器生成类型,不能在包kotlin.reflect中找到它们的声明。意味着可以使用任意数量参数的函数接口。
属性KProperty
KProperty与Java的Field不同,它通称代表相应的Getter和Setter,而Java的Field通常指字段本身。
KProperty可以表示任何属性,而它的子类KMutableProperty可以表示一个var声明的可变变量。
KProperty的超类型也是KCallable,意味着当获取该属性时,也可以使用KCallable#call,KCallable#call会调用该属性的getter。但Kotlin的属性接口提供了一个更好的获取属性值的方式:get方法。
KProperty0<out R>提供的是一个无参的get方法,其类型参数代表属性的类型;
例如顶层属性,直接使用::操作符引用。因为无需接收者,其属性引用的类型为 KProperty0.
val name = "daqi"
fun main(){
val property = ::name
property.get()
property.call()
}
KProperty1<T, out R>提供一个需要接收者的get方法,其一个类型参数代表接收者的类型,第二个类型参数代表属性的类型;
例如 成员属性 或 扩展属性 ,需要一个具体的接收者实例,其属性引用的类型为 KProperty1
fun main(){
val property = daqiKotlin::name
val daqi = daqiKotlin()
property.get(daqi)
}
class daqiKotlin{
val name = ""
}
属性引用只能用于定义在外层或类中的属性,而不能用于局部变量。
KMutableProperty作为KProperty子类,除了提供对应get方法外,还提供了set方法。
fun main(){
val property = daqiKotlin::name
val daqi = daqiKotlin()
property.get(daqi)
property.set(daqi,"")
property.call(daqi)
}
class daqiKotlin{
var name = ""
}
KMutableProperty实例的call方法仍然是调用该属性的getter。
最后
Kotlin基础知识归纳系列到本章结束,这一路过来自己也弄清楚了很多概念,Kotlin基础知识更牢固了。同时也深知自己文笔粗旷,往后会一步步改进的,让文章的读者更容易理解文章内容。
最后推荐3篇很不错的Kotlin文章:
参考资料:
- 《Kotlin实战》
- Kotlin官网