反射
Kotlin中属性和函数与对象一样都是一等公民,可以直接通过反射获取其引用;
1.1、类引用
Kotlin中的类引用用KClass表示,引用的是KClass对象,Java的类引用是java.lang.Class对象,二者不一样;
对于已知的Kotlin类,通过以下获取其类引用:
var c=MyClass(类名)::classs
如果已有Kotlin对象,也可使用对象获取类引用:
var c=clz::class
如果要通过KClass获取对应的Java类引用:调用KClass对象的java属性
c.java;
1.2、反射创建实例
方式一:
- 获取到KClass对象后,可以调用其createInstance()方法创建该类的实例,该方法***总是调用该列无参构造器创建的实例***,因此使用该方法的前提就是必须提供无参构造器;
方式二:
通过获取到的KClass对象的,借助其constructors获取到所有构造器,再利用条件判断返回目标构造器,最后通过目标构造器对象的call()创建出对应的实例;
class Item(var name:String){
var price=0.0
constructor(name: String,price:Double):this(name){
this.price=price
}
constructor():this("未知商品"){
}
}
fun main(args: Array<String>) {
val itemClz= Item::class;
val instance = itemClz.createInstance()
println("createInstance()调用无参构造器创建实例")
println(instance.name)
println(instance.price)
println("调用有参构造器创建实例")
itemClz.constructors.forEach {
if(it.parameters.size==2){
val item = it.call("酸奶", 9.0)
println(item.name)
println(item.price)
}
}
}
1.3、构造器引用
构造器本质也是函数,即一个返回值为当前类实例的函数,可以将构造器引用当成函数使用;Kotlin允许通过"::“操作符并添加类名来引用***主构造器***(”::类名")
比如:上面的两个参数构造器,其函数类型为(String,Double)->Item
class Fruit(var name:String="未知"){
fun test(apple:(String)->Fruit){
val apple1: Fruit= apple("苹果")
println("Fruit实例name属性:${apple1.name}")
}
}
fun main(args: Array<String>) {
val fruit = Fruit()
fruit.test(::Fruit)
}
和函数作为方法形参也一样,当把函数赋值给变量时,通过"::函数名"既可,构造器大同小异,是通过"::类名"将构造器赋值给变量;
需要说明的是:如果要调用Kotlin构造器引用对应的Java构造器对象,则可通过KFunction的扩展属性javaConstructor来实现;
如:
::Fruit.javaConstructor
调用构造器引用的javaCostructor属性,需要导入Kotlin.reflect.jvm包,这些扩展属性属于与Java反射互相操作的部分;
1.4、调用方法
所有构造器和方法都是KFunction的实例,调用他们可以通过其call()方法;
调用方法步骤:
- 获取方法对象(KFunction的实例)
- 调用方法对象的call()
方法转函数:
方法是面向对象的,必须有主谓宾如(“猪八戒.吃(西瓜)”),因此在转函数时其形参都是会比对应方法多一个参数即:主语(方法调用者),就变了"吃(猪八戒,西瓜)"
class Foo{
fun test(msg:String){
println("执行带参方法,mag=$msg")
}
}
fun main(args: Array<String>) {
val kclz= Foo::class
val instance = kclz.createInstance()
kclz.declaredMemberFunctions.forEach {
if(it.parameters.size==2){
it.call(instance,"Hello Kotlin")
}
}
}
1.5、函数引用
我们多次说过,函数也是一等公民,将函数赋值给引用是“::函数名”,调用函数:“引用变量(实参)”或者函数后面直接添加“()”同样表示调用函数;这儿在赋值给引用是,如果该函数存在多个重载的函数,那么Kotlin通过上下文推断调用的哪个函数;如果无法推断出引用哪个函数,编译器报错;
fun isSmall(i:String)=i.length<5
fun isSmall(i:Int)=i<5
当我们将函数赋值给未显示指定变量类型的引用,就会报错;
var f=::isSmall
//这儿就必须显示声明变量类型,正确写法
var f:(String)->Boolean=::isSmall
还需要补充一点的就是:如果将成员方法或扩展方法赋值给引用,需要使用限定:***“类名::成员/扩展方法"***。方法类型也不是函数的“(形参类型)->返回值类型”类型,同样需要使用限定即***”类名.(形参类型)->返回值类型“***
总结:
- 函数赋值给引用变量是"::函数名",该变量类型为函数类型是(形参类型)->返回值类型,
- 类方法、扩展方法赋值给引用变量时是"类名::方法名",该变量类型是"类名.(形参类型)->返回值类型"
如果要获取Kotlin函数引用的java方法对象,可以通过KFunction的扩展属性javaMethod实现:
::方法名.javaMeethod
但是需要导入kotlin.reflect.jvm 包
总结:函数与方法引用
- 包级函数引用:直接通过"::函数名"赋值给引用变量 函数类型:(参数类型)->返回值类型
- 类成员方法与扩展方法:通过“类名::方法名”赋值给引用变量, 函数类型为:“类名.(参数类型)->返回值类型”
1.6 访问属性值
获取到KClass对象后,通过该对象来获取该类包含的属性;
- KProperty:代表***通用的属性***,他是KCallable的子接口
- KMutableProperty:代表通用的读写属性,他是KProperty的子接口
- KProperty0:代表无需调用者的属性(静态属性),他是KPropety的子接口
- KMutableProperty0:代表无需调用者的读写属性(静态读写属性),他是Kproperty的子接口
- Kproperty1:代表需要1个调用者的属性(成员属性),他是KProperty的子接口
- KMutableProperty1:代表一个调用者的读写属性(成员读写属性)他是KProperty1的子接口;
- Kproperty2:代表需要两个调用者的属性(扩展属性),他是KProperty的子接口
- KMutableProperty2:代表需要2个调用者的读写属性(扩展读写属性),他是KProperty2的子接口
从命名字面意思就可以知道:Mutable:表示读写属性,KPropertyN中的数字表示:0:表示无需调用者即静态属性,1:成员属性,2:表示扩展属性
当获取到KProperty对象后,可以调用get()方法获取该属性的值,如果是读写属性,需要设置值,则需要获取代表读写属性KMutableProperty对象,调用set()
1.7、属性引用
Kotlin同样提供了“::”符号加属性名的形式获取属性引用;获取的属性引用属于前面的KProperty及其子类接口实例;
获取到的读写属性引用,可以调用set()、get()修改、获取属性值,只读属性可以通过get()方法获取属性值
包级属性:“::属性名”
类成员属性:“类名::属性名”
在kotlin.reflect.jvm包下提供了Kotlin属性与java反射互操作的扩展属性:
kotlin属性对应java中的字段、getter、setter方法三种成员;
Kproperty类包含三个扩展属性:
- javaField:获取该属性的幕后字段(如果该属性有幕后字段),该属性返回java.lang.reflect.Field对象
- javaGetter:获取该属性的getter方法,该属性返回java.lang.reflect.Method对象
- javaSetter:获取该属性的setter方法(如果该属性是读写属性),该属性返回java.lang.reflect.Method对象
1.8、绑定的方法与属性引用
前面我们在获取***类中***静态属性、实例属性、静态方法、实例方法引用时,都是直接通过类名限定(类名::方法名、属性名),在获取到方法引用、属性引用时,无论是调用方法或者设置属性值时第一个参数***必须传入该方法、属性的调用者即该方法、属性所属对象实例***(参数个数=等于形参个数+方法调用者);Kotlin1.1支持绑定的方法与属性引用,方法、属性引用不是通过类(名)获取(如:类名::方法名),而是通过对象获取(如:“对象名::方法/属性名”),这样在调用该方法是将不用在传入该方法的调用者了(其实就是获取实例方法、实例属性引用);
var str="kotlin"
var f:(CharSequence,Boolean)->Boolean=str::endWith
// 无须再传入方法调用者
调用:f("lin",true)