Kotlin反射

作者: 程序员丶星霖 | 来源:发表于2019-12-06 09:47 被阅读0次

    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

    相关文章

      网友评论

        本文标题:Kotlin反射

        本文链接:https://www.haomeiwen.com/subject/ljldkctx.html