原创

Kotlin反射

Kotlin把函数和属性当成“一等公民”,并可通过反射直接获取函数、属性的引用。

一、类引用

Kotlin的类引用使用KClass代表,如果想要获取已知的Kotlin类的KClass对象,可通过如下语法:

val  c  =  MyClass : : class

如果已有一个Kotlin对象,同样可以通过 : : class语法来获取该对象的类引用。

二、从KClass获取类信息

获取KClass对象之后,可以通过KClass提供的大量方法或属性来获取该KClass对象所对应的详细信息。

package test0711

import kotlin.reflect.full.*

//定义注解
annotation class Anno

//使用3个注解修饰该类
@Deprecated("该类已经不推荐使用")
@Anno
@Suppress("UNCHECKED_CAST")
class KClassTest(age: Int) {
    var name: String = "Kotlin"

    //为该类定义一个私有的构造器
    private constructor() : this(20) {}

    //定义一个有参数的构造器
    constructor(name: String) : this(15) {
        println("执行有参数的构造器:${name}")
    }

    //定义一个无参数的info方法
    fun info() {
        println("执行无参数的info方法")
    }

    //定义一个有参数的info方法
    fun info(str: String) {
        println("执行有参数的info方法,其str参数值:${str}")
    }

    //定义一个测试用的嵌套类
    class Inner
}

//为KClassTest定义扩展方法
fun KClassTest.bar() {
    println("扩展的bar方法")
}

//为KClassTest定义扩展属性
val KClassTest.foo: Double
    get() = 2.4

fun main(args: Array<String>) {
    //获取KClassTest对应的KClass
    val clazz = KClassTest::class
    //通过constructors属性获取KClass对象锁对应的全部构造器
    val ctors = clazz.constructors
    println("KClassTes的全部构造器如下:")
    ctors.forEach { println(it) }
    println("ClassTest的主构造器如下:")
    println(clazz.primaryConstructor)

    //通过functions属性获取该KClass对象所对应类的全部方法
    var funs = clazz.functions
    println("KClassTest的全部方法如下:")
    funs.forEach { println(it) }

    //通过declaredFunctions属性获取该KClass对象声明的全部方法
    var funs2 = clazz.declaredFunctions
    println("KClassTest本身声明的全部方法如下:")
    funs2.forEach { println(it) }

    //通过declaredMemberFunctions属性获取全部成员方法
    var memberFunctions = clazz.declaredMemberFunctions
    println("KClassTest本身声明的成员方法如下:")
    memberFunctions.forEach { println(it) }

    //通过memberExtensionFunctions属性获取全部扩展方法
    var exetensionFunctions = clazz.memberExtensionFunctions
    println("KClassTest声明的扩展方法如下:")
    exetensionFunctions.forEach { println(it) }

    //通过decaredMemberProperties获取全部成员属性
    var memberProperties = clazz.declaredMemberProperties
    println("KClassTest本身声明的成员属性如下:")
    memberProperties.forEach { println(it) }

    //通过memberExtensionProperties属性获取该KClass对象的全部扩展属性
    var exProperties = clazz.memberExtensionProperties
    println("KClassTest本身声明的扩展属性如下:")
    exProperties.forEach { println(it) }

    //通过annotations属性获取该KClass对象所对应类的全部注解
    val anns = clazz.annotations
    println("KClassTest的全部注解如下:")
    anns.forEach { println(it) }
    println("该KClass元素上的@Annot注解为:${clazz.findAnnotation<Anno>()}")

    //通过nestedClasses属性获取所对应的全部嵌套类
    val inners = clazz.nestedClasses
    println("KClassTest的全部内部类如下:")
    inners.forEach { println(it) }

    //通过supertypes属性获取该类的所有父类型
    println("KClassTest的父类型为:${clazz.supertypes}")
}

三、创建对象

获取KClass对象之后,调用该对象的createInstance()方法即可创建该类的实例。

package test0711

import kotlin.reflect.full.createInstance

class Item(var name: String) {
    var price = 0.0

    constructor() : this("未知商品") {
        this.price = 0.0
    }

    constructor(name: String, price: Double) : this(name) {
        this.price = price
    }
}

fun main(args: Array<String>) {
    val clazz = Item::class
    //createInstance()方法调用无参数的构造器创建实例
    val inst1 = clazz.createInstance()
    println(inst1.name)
    println(inst1.price)
    //获取所有构造器
    val cons = clazz.constructors
    cons.forEach {
        if (it.parameters.size == 2) {
            //调用带两个参数的构造器创建实例
            val inst2 = it.call("Kotlin", 45.6)
            println(inst2.name)
            println(inst2.price)
        }
    }
}

四、构造器引用

构造器本质是一个函数,即一个返回值为当前类实例的函数。

Kotlin允许通过使用": :"操作符并添加类名来引用该类的主构造器。

package test0711

class Foo(var name: String = "未知")

//test函数的参数是(String)->Foo类型
fun test(factory: (String) -> Foo) {
    val x: Foo = factory("Kotlin")
    println(x.name)
}

fun main(args: Array<String>) {
    //通过::Foo引用Foo类的主构造器
    test(::Foo)
}

如果要获取Kotlin构造器引用对应的Java构造器对象,可通过调用KFunction的扩展属性javaConstructor来实现。

: : Foo.javaConstructor 

五、调用方法

所有构造器和方法都属于KFunction的实例,可以通过call()方法来调用。

要调用指定类的方法,要先获取方法的KFunction实例,然后调用call()方法即可。

package test0711

import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredFunctions

class CallFunction {
    fun test(msg: String) {
        println("执行带String参数的test方法:${msg}")
    }

    fun test(msg: String, price: Double) {
        println("执行带String,Double参数的test方法:${msg},${price}")
    }
}

fun main(args: Array<String>) {
    val clazz = CallFunction::class
    //创建实例
    val ins = clazz.createInstance()
    //获取clazz所代表类直接定义的全部函数
    val funs = clazz.declaredFunctions
    for (f in funs) {
        //如果函数具有3个参数
        if (f.parameters.size == 3) {
            //调用3个参数的函数
            f.call(ins, "Kotlin", 45.6)
        }

        //如果函数具有2个参数
        if (f.parameters.size == 2) {
            //调用带2个参数的函数
            f.call(ins, "Kotlin")
        }
    }
}

六、函数引用

Kotlin的函数也有自身的类型。Kotlin可以获取函数的引用,把函数当成参数传入另一个函数中。

Kotlin也可通过“: :”符号加函数名的形式来获取特定函数的引用。

package test0711

//定义两个重载的函数
fun isSmall(i: Int) = i < 5

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

fun main(args: Array<String>) {
    val list = listOf(20, 30, 100, 4, -3, 2, -12)

    val resultList = list.filter(::isSmall)
    println(resultList)

    val strList = listOf("Java", "Kotlin", "Swift", "Go", "Erlang")

    val resultStrList = strList.filter(::isSmall)
    println(resultStrList)

    var f: (String) -> Boolean = ::isSmall
    println(f("Lua"))
}

下面示范利用函数引用来组合两个函数

package test0711

fun abs(d: Double): Double = if (d < 0) -d else d
fun sqrt(d: Double): Double = java.lang.Math.sqrt(d)

//定义一个comp()函数,该函数用于将两个函数组合起来
fun comp(fun1: (Double) -> Double, fun2: (Double) -> Double):
            (Double) -> Double {
    return { x -> fun2(fun1(x)) }
}

fun main(args: Array<String>) {
    println(abs(-3.2))
    //将::abs和::sqrt组合起来
    val f = comp(::abs, ::sqrt)
    println(f(-25.0))
}

如果要获取Kotlin函数引用对应的Java方法对象,则可通过调用KFunction的扩展属性javaMethod来实现。

: : abs.javaMethod

七、访问属性值

获取KClass对象之后,也可通过KClass对象来获取该类所包含的属性。

  • KProperty:代表通用的属性。
  • KMutableProperty:代表通用的读写属性。
  • KProperty():代表无需调用者的属性。
  • KMutableProperty():代表无需调用者的读写属性。
  • KProperty1:代表需要1个调用者的属性。
  • KMutableProperty1:代表需要1个调用者的读写属性。
  • KProperty2:代表需要2个调用者的属性。
  • KMutableProperty2:代表需要2个调用者的属性。
package test0711

import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberExtensionProperties

class CallItem {
    var name: String = "Kotlin"
    val price: Double = 7.6
}

fun main(args: Array<String>) {
    val calzz = CallItem::class
    val ins = calzz.createInstance()
    val props = calzz.declaredMemberExtensionProperties
    props.forEach {
        when (it.name) {
            "name" -> {
                @Suppress("UNCHECKED_CAST")
                //将属性转换为读写属性
                val mp = it as KMutableProperty1<CallItem, Any>
                //修改属性值
                mp.set(ins, "Java")
                println(it.get(ins))
            }
        }
    }
}

八、属性引用

Kotlin同样提供了“: :”符号加属性名的形式来获取属性引用。

获取Kotlin只读属性的引用之后,可调用get()方法来获取属性的值;获取Kotlin读写属性的引用之后,程序可调用set()方法来修改属性值,也可调用get()方法来获取属性的值。

package test0711

class PropertyItem {
    var name: String = "Kotlin"
    val price: Double = 12.3
}

var ftest = "测试属性"

fun main(args: Array<String>) {
    //获取foo属性
    val topProp = ::ftest
    topProp.set("修改后的属性")
    println(topProp.get())

    val im = PropertyItem()
    //获取PropertyItem的name属性
    val mp = PropertyItem::name
    mp.set(im, "Java")
    println(mp.get(im))

    //获取PropertyItem的price属性
    val prop = PropertyItem::price
    println(prop.get(im))
}

由于Kotlin属性会对应于Java的3种成员,因此KProperty包下包含如下3个扩展属性:

  • JavaField:获取该属性的幕后字段。
  • javaGetter:获取该属性的getter方法。
  • JavaSetter:获取该属性的setter方法。
package test0711

import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.javaSetter

class K2JItem {
    var name: String = "Kotlin"
    val price: Double = 12.3
}

var k2jtest = "测试属性"

fun main(args: Array<String>) {
    //获取属性
    val topProp = ::k2jtest
    println(topProp.javaField)
    println(topProp.javaGetter)
    println(topProp.javaSetter)

    //获取K2JItem的name属性
    val mp=K2JItem::name
    println(mp.javaField)
    println(mp.javaGetter)
    println(mp.javaSetter)

    //获取K2JItem的price属性
    val prop=K2JItem::name
    println(prop.javaField)
    println(prop.javaGetter)
}

九、绑定的方法与属性引用

Kotlin支持一种“绑定的方法或属性引用”,这种方法或属性引用不是通过类获取的,而是通过对象获取的,这就意味着该方法或属性已经绑定了调用者,因此执行时无须传入调用者。

package test0711

fun main(args: Array<String>) {
    val str = "Kotlin"
    //获取对象绑定的方法
    val f: (CharSequence, Boolean) -> Boolean = str::endsWith
    //调用绑定的方法时无须传入调用者
    println(f("lin", true))
    //获取对象绑定的属性
    val prop = str::length
    //调用绑定的属性时无须传入调用者
    println(prop.get())

    var list = listOf("Kotlin", "Java", "Go", "Erlang")
    //获取对象的绑定方法
    val fn = list::subList
    //调用绑定的方法时无须传入调用者
    println(fn(1, 3))
    //获取对象绑定的属性
    val prp = list::indices
    //调用绑定的属性时无须传入调用者
    println(prp.get())
}

输出结果:

true
6
[Java, Go]
0..3

学海无涯苦作舟

我的微信公众号.jpg

基本语法
电子书收集
  • 作者:HunterArley (联系作者)
  • 发表时间:2019-12-06 09:50
  • 版权声明:本网站部分内容转载于合作站点或其他站点,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
  • 公众号转载:请在文末添加作者公众号二维码