美文网首页
Kotlin和Java互相调用(二)

Kotlin和Java互相调用(二)

作者: 凌寒天下独自舞 | 来源:发表于2018-11-08 10:57 被阅读0次

    Kotlin反射

    正如在前面不少代码中所见到的, Kotlin也提供了反射 API,这些反射 API可以方便程序在运行时自省程序的结构 。 Kotlin把函数和属性当成“ 一等公民”, 并可通过反射直接获取函数、属性的引用。

    使用 Kotlin的反射API需要添加单独的JAR文件(kotlin-reflect.jar),这样可以方使程序在不使用反射时减小运行库的大小。

    类引用

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

    val c = MyClass : :class
    

    上面这种语法在前面程序中已经多次见到。需要说明的是,Kotlin的类引用是 KClass 对象, Java 的类引用是 java.lang.Class 对象,它们二者是不同的,如果需要通过KClass 获取对应的 java.lang.Class 对象,则可调用 KClass 对象的 java 属性 。
    如果己有一个Kotlin对象, 则同样可通过::class语法来获取该对象的类引用。例如如下语法:

    val c = myObj : :class
    

    从KClass获取类信息

    获取 KClass对象之后,即可通过KClass提供的大量方法或属性来获取该 KClass对象所对应类的详细信息。
    下面程序示范了通过 KClass 来获取类的详细信息。该程序示范了 KClass 所包含的部分属性和方法

    import kotlin.reflect.full.*
    
    //定义注解
    annotation class Anno
    
    //使用 3 个注解修饰该类
    @Deprecated("该类已经不推荐使用 ")
    @Anno
    @Suppress(" UNCHECKED CAST")
    class ClassTest(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
    }
    
    //为 ClassTest 定义扩展方法
    fun ClassTest.bar() {
        println("扩展的 bar 方法")
    }
    
    //为 ClassTest 定义扩展属性
    val ClassTest.foo: Double
        get() = 2.4
    
    fun main(args: Array<String>) {
        //下面代码可以获取 ClassTest 对应的 KClass
        val clazz = ClassTest::class
        //通过 constructors 属性获取 KClass 对象所对应类的全部构造器
        val ctors = clazz.constructors
        println("ClassTest 的全部构造器如下: ")
        ctors.forEach {
            println(it)
        }
        println("ClassTest 的主构造器如下: ")
        println(clazz.primaryConstructor)
    
        //通过 functions 属性获取该 KClass 对象所对应类的全部方法
        var funs = clazz.functions
        println("ClassTest 的全部方法如下: ")
        funs.forEach {
            println(it)
        }
    
        //通过 declaredFunctions 属性获取该 KClass 对象 本身所声明的全部方法(不包括继承的方法)
        val funs2 = clazz.declaredFunctions
        println("ClassTest 本身声明的全部方法如下: ")
        funs2.forEach {
            println(it)
        }
    
        //通过 declaredMemberFunctions 属性获取该 KClass 对象 本身所声明的全部成员方法(不包括继承的方法〉
        val memberFunctions = clazz.declaredMemberFunctions
        println("ClassTest 本身声明的成员方法如下: ")
        memberFunctions.forEach {
            println(it)
        }
    
        //通过 memberExtensionFunctions 属性获取该 KClass 对象 所代表类的全部扩展方法(不包括继承的方法)
        val extensionFunctions = clazz.memberExtensionFunctions
        println("ClassTest 本身声明的扩展方法如下: ")
        extensionFunctions.forEach {
            println(it)
        }
    
        //通过 declaredMemberProperties 属性获取该 KClass 对象 本身所声明的全部成员属性(不包括继承的属性〉
        val memberProperties = clazz.declaredMemberProperties
        println("ClassTest 本身声明的成员属性如下: ")
        memberProperties.forEach {
            println(it)
        }
    
        //通过 memberExtensionProperties 属性获取该 KClass 对象 所代表类的全部扩展属性(不包括继承的属性〉
        val extensionProperties = clazz.memberExtensionProperties
        println("ClassTest 本身声明的扩展属性如下: ")
        extensionProperties.forEach {
            println(it)
        }
    
        //通过 annotations 属性获取该 KClass 对象所对应类的全部注解
        val anns = clazz.annotations
        println("ClassTest 的全部注解如下: ")
        anns.forEach {
            println(it)
        }
    
        println("该 KClass 元素上的@Anno注解为: "+ clazz.findAnnotation<Anno>())
    
        //通过 nestedClasses 属性获取该 KClass 对象所对应类的全部嵌套类(包括内部类和嵌套类)
        val inners = clazz.nestedClasses
        println("ClassTest 的全部内部类如下: ")
        inners.forEach {
            println(it)
        }
    
        //通过 supertypes 属性获取该类的所有父类型(包括父类和父接口)
        println("ClassTest 的父类型为: " + clazz.supertypes)
    }
    

    运行结果:

    ClassTest 的全部构造器如下:
    fun <init>(): test11.ClassTest
    fun <init>(kotlin.String): test11.ClassTest
    fun <init>(kotlin.Int): test11.ClassTest
    ClassTest 的主构造器如下:
    fun <init>(kotlin.Int): test11.ClassTest
    ClassTest 的全部方法如下:
    fun test11.ClassTest.info(): kotlin.Unit
    fun test11.ClassTest.info(kotlin.String): kotlin.Unit
    fun kotlin.Any.equals(kotlin.Any?): kotlin.Boolean
    fun kotlin.Any.hashCode(): kotlin.Int
    fun kotlin.Any.toString(): kotlin.String
    ClassTest 本身声明的全部方法如下:
    fun test11.ClassTest.info(): kotlin.Unit
    fun test11.ClassTest.info(kotlin.String): kotlin.Unit
    ClassTest 本身声明的成员方法如下:
    fun test11.ClassTest.info(): kotlin.Unit
    fun test11.ClassTest.info(kotlin.String): kotlin.Unit
    ClassTest 本身声明的扩展方法如下:
    ClassTest 本身声明的成员属性如下:
    var test11.ClassTest.name: kotlin.String
    ClassTest 本身声明的扩展属性如下:
    ClassTest 的全部注解如下:
    @kotlin.Deprecated(level=WARNING, replaceWith=@kotlin.ReplaceWith(imports={}, expression=""), message="该类已经不推荐使用 ")
    @test11.Anno()
    该 KClass 元素上的@Anno注解为: @test11.Anno()
    ClassTest 的全部内部类如下:
    class test11.ClassTest$Inner
    ClassTest 的父类型为: [kotlin.Any]

    值得指出的是,虽然定义ClassTest类时使用了@Suppress 注解,但程序运行时无法分析出该类中包含的该注解,这是因为@Suppress 使用了@Retention(SOURCE)修饰,这表明 @Suppress 只能保存在源代码级别上,而通过 ClassTest.class 获取的是该类的运行时 KClass 对象,所以程序无法访问到@ Suppress 注解。

    通过 KClass对象可以得到大量的 KFunction、 KProperty (它们都是 KCallable 的子类)等对象,这些对象分别代表该类所包括的方法(包括构造器)和属性等,程序还可以通过这些对象来执行实际的功能,例如调用方法、创建实例等。

    创建对象

    获取 KClass 对象之后,调用该对象的 createlnstance()方法即可创建该类的实例,该方法总是调用 KClass所代表类的无参数的构造器来创建实例。
    如果需要调用有参数的构造器来创建实例,则可通过 KClass 的 constructors 属性来获取所有构造器,该属性返回 Collection<Function>集合对象,这意味着构造器的本质依然是一个函 数。
    获取 KClass 的所有构造器之后,接下来程序可根据需要调用指定的构造器来创建实例。下面程序示范了如何通过 KClass 创建实例 。

    import kotlin.reflect.full.*
    
    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()
        //未知商品
        //0.0
        println(inst1.name)
        println(inst1.price)
    
        //获取所有构造器
        val cons = clazz.constructors
        cons.forEach {
            if (it.parameters.size == 2) {
                //调用带两个参数的构造器创建实例
                val inst2 = it.call("kotlin",78.9)
                //kotlin
                //78.9
                println(inst2.name)
                println(inst2.price)
            }
        }
    }
    

    构造器引用

    正如前文所介绍的,构造器的本质是一个函数,即一个返回值为当前类实例的函数。 因此程序可将构造器引用当成函数使用。
    此外, Kotlin 允许通过使用“::”操作符并添加类名来引用该类的主构造器 。 例如如下程序。

    class Foo(var name: String = "未知")
    
    //test 函数的参数是(String)->Foo 类型(这就是 Foo 带 String 参数的构造器的类型)
    fun test(factory: (String) -> Foo) {
        val x: Foo = factory("疯狂 Kotlin 讲义")
        println(x.name)
    }
    
    fun main(args: Array<String>) {
        //通过: :Foo 引用 Foo 类的主构造器
        test(::Foo)
    }
    

    上面代码调用 test()函数时需要传入一个(String)->Foo 类型的参数,这就是 Foo 类主构造器的类型,因此将::Foo 作为参数传入 。
    在某些时候,如果要获取 Kotlin构造器引用对应的 Java构造器对象(Constructor),则可通过调用 KFunction 的扩展属性 javaConstructor来实现。
    例如如下代码:

    ::Foo. javaConstructor
    

    需要说明的是,如果要调用构造器引用的 javaConstructor属性,则需要导入kotlin.reflect.jvm包,因为这些扩展属性都属于与 Java反射互相操作的部分,被定义在 kotlin.reflect.jvm包下。

    调用方法

    正如前面所见到的,所有构造器和方法都属于KFunction的实例,因此它们都可以通过call() 方法来调用。 所以,程序要调用指定类的方法,只要先获取方法的KFunction实例,然后调用call()方法即可 。
    使用 KFunction调用方法时,有一点需要说明: 由于方法是面向对象的概念,因此它有一 个主调者。比如 一句汉语:“猪八戒吃西瓜”,换成面向对象的写法就是 :

    猪八戒 .吃(西瓜)

    但如果换成函数式写法就是 :

    吃(猪八戒,西瓜)

    对比这两种写法可以看出,面向对象的方法如果带 N个参数,那么转换成函数式调用时就会变成 N+I 个参数。
    如下程序示范了调用指定函数。

    class Foo1 {
        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 = Foo1::class
        //创建 Foo 类的实例
        val ins = clazz.createInstance()
        //获取 clazz 所代表类直接定义的全部函数
        val funs = clazz.declaredFunctions
        for (f in funs) {
            //如果函数具有 3 个参数(对应带 2 个参数的方法)
            if (f.parameters.size == 3) {
                //调用带 3 个参数的函数 
                //执行带String, Double参数的 test方法:  Kotlin  78.8
                f.call(ins, "Kotlin", 78.8)
            }
    
            //如果函数具有 2 个参数(对应带 1 个参数的方法〉
            if (f.parameters.size == 2) { 
                //调用带 2 个参数的函数
                //执行带 String 参数的 test 方法 : Kotlin
                f.call(ins, "Kotlin")
            }
        }
    }
    

    从上面程序可以看出 , 执行带3个参数的函数实际上是调用带2个参数的方法,执行带2个参数的函数实际上是调用带1个参数的方法,方法的调用者将作为函数的第一个参数被传入。

    函数引用

    Kotlin 的函数也是一等公民,函数也有其自身的类型。Kotiin 程序可以获取函数的引用,把函数当成参数传入另一个函数中。
    Kotiin 也通过“::”符号加函数名的形式来获取特定函数的引用。当存在多个重载函数时, Kotlin可通过上下文推断出实际引用的是哪个函数: 如果 Kotiin无法通过上下文准确推断出引用哪个数,编译器就会报错。例如如下程序。

    //定义两个重载的函数
    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)
    
        //由于 filter ()函数需要(Int) ->Boolean 类型的参数,故此处 ::isSmall 引用第一个函数
        val resultList = list.filter(::isSmall)
        println(resultList) //输出[ 4, - 3, 2, -12]
    
        val strlist = listOf("Java", "Kotlin", "Swift", "Go", "Erlang")
        //由于 filter () 函数需要(String)->Boolean 类型的参数, 故此处 ::isSmall 引用第二个函数
        val resultStrList = strlist.filter(::isSmall)
        println(resultStrList) //输出[ Java , Go]
        //无法推断出 :: isSmall 到底引用哪个函数,报错
        //val f = ::isSmall
        //可以推断出 :: isSmall 到底引用哪个函数,正确
        var f: (String) -> Boolean = ::isSmall
        println(f("Lua"))
    }
    

    从代码所看到的 , 当 List 集合是 List<String>实例时,它的 filter()方法需要的参数是(String)->Boolean类型,因此 Kotlin可以准确地从重载函数中引用到符合要求的函数 。
    此外需要说明的是,如果 需要引用类的成员方法或扩展方法,那么需要进行限定。例如 String::toCharArray才能表明引用 String的 toCharArray()方法,单纯地使用 ::toCharAηay()不行 。
    String::toCharArray 函数引用的类型也不是简单的()->CharArray 类型,而是 String.() -> CharArray 类型 。

    有些时候,程序需要实现某个功能较强的函数, 如果此时系统己经包含了多个细粒度的函数,那么可以将这些细粒度的函数组合起来实现功能较强的函数。 比如业务需要程序获取数的平方根,但该函数要做一些额外处理,如果该数是正数,则直接获取平方根;如果该数是负数,则获取该数的绝对值的平方根。

    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))
    }
    

    上面代码用 comp()函数将::abs和::sqrt两个函数组合在一起,这样就会得到 一个新的函数 : f, 接下来程序可通过f()函数同时完成两个函数的功能。

    在某些时候,如果要获取 Kotlin 函数引用对应的 Java 方法对象( Method),则可通过调用 KFunction 的扩展属性 javaMethod 来实现 。例如如下代码:

    :: abs.javaMethod
    

    需要说明的是,如果要调用函数引用的 javaMethod 属性 ,则需要导入kotlin.reflect.jvm包,因为这些扩展属性都属于与Java 反射互相操作的部分,被定义在 kotlin.reflect.jvm包下。

    访问属性值

    获取 KClass 对象之后,也可通过 KClass对象来获取该类所包含的属性。 Kotlin为属性提供了众多的 API。

    • KProperty: 代表通用的属性 。 它是 KCallable 的子接口。
    • KMutableProperty: 代表通用的读写属性。它是 KProperty的子接口。
    • KProperty0: 代表无需调用者的属性(静态属性)。它是 KProperty的子接口 。
    • KMutableProperty0: 代表无需调用者的读写属性(静态读写属性)。它是 KProperty0的子接口 。
    • KProperty1 : 代表需要 1 个调用者的属性(成员属性)。它是 KProperty的子接口。
    • KMutableProperty1:代表需要 1个调用者的读写属性(成员读写属性)。它是 KProperty1的子接口。
    • KProperty2:代表需要 2 个调用者的属性(扩展属性) 。 它是 KProperty的子接口。
    • KMutableProperty2:代表需要 2个调用者的读写属性(扩展读写属性)。它是 KProperty2的子接口。

    程序获取代表属性的 KProperty对象之后,可调用 get()方法来获取属性的值;如果程序要设置属性的值,则需要获取代表属性的 KMutableProperty 对象。
    如下程序示范了如何通过反射来设置和获取属性的值 。

    class Item2 {
        var name: String = "kotlin"
        val price: Double = 24.1
    }
    
    fun main(args: Array<String>) {
        val clazz = Item2::class
        val ins = clazz.createInstance()
        val props = clazz.declaredMemberProperties
        props.forEach {
            when (it.name) {
                "name" -> {
                    @Suppress("UNCHECKED_CAST")
                    //将属性转换为读写属性
                    val mp = it as KMutableProperty1<Item2, Any> //修改属性值
                    mp.set(ins, "Java")
                    println(it.get(ins))
                }
    
                "price " -> {
                    //只读属性,只能通过 get ()方法读取属性值
                    println(it.get(ins))
                }
            }
        }
    }
    

    正如上面代码所看到的,当程序要设置 name 属性的属性值时,由于 name 属性是一个成员读写属性,因此程序将该属性转型为 KMutableProperty1 对象,转型之后程序可调 用 set()方法来设置该属性的值,如上面代码所示:如果程序只需获取该属性的值,则调用 KProperty1 的 get()方法即可,如上面代码所示。

    属性引用

    Kotiin 同样提供了“::”符号加属性名的形式来获取属性引用,获取属性引用也属于前面介绍的Kproperty 及其子接口的实例 。
    获取 Kotiin 只读属性的引用之后,程序可调用 get()方法来获取属性的值;获取 Kotiin读写属性的引用之后,程序可调用 set()方法来修改属性的值,也可调用 get()方法来获取属性的值。如下程序示范了通过属性引用来操作属性。

    class Item3 {
        var name: String = "kotlin"
        var price = 16.1
    }
    
    var foo = "foo"
    
    fun main(args: Array<String>) {
        //获取 foo 属性,属于 KMutablePropertyO 的实例
        val topProp = ::foo
        topProp.set("修改后的属性")
        // println(topProp.get())
        println(foo)
    
        val im = Item3()
        //获取 Item 的 name 属性,属于 KMutablePropertyl1的实例
        var namePro = Item3::name
        namePro.set(im, "xq")
        println(namePro.get(im))
    
        //获取 Item 的 price 属性,属于 KProperty1 的实例
        val prop = Item3::price
        println(prop.get(im))
    }
    

    上面代码获取顶级读写属性(静态读写属性),程序直接用“::”加 属性名的形式即可。该属性引用是 KMutableProperty0 的实例 , 因此该属性既可通过 set()方法 改变属性的值,也可通过 get()方法获取属性的值。
    第代码获取 Item类的name读写属性,程序需要用类名::属性名的形式来获取指定类的读写属性。该属性是 KMutableProperty1 的实例,因此程序也可通过 set()、 get()方法来修改、获取属性的值 。

    与前面介绍的构造器、函数相似, Kotiin 在 kotlin.reflect.jvm 包下也提供了Kotlin 属性与Java 反射互操作的扩展属性 。 由于 Kotlin 属性会对应于 Java 的 3 种成员,因此 KProperty 包含如下 3 个扩展属性 。

    • javaField : 获取该属性的幕后字段(如果该属性有幕后字段的话)。 该属性返回java.lang.reflect.Field对象。
    • javaGetter : 获取该属性的 getter方法 。 该属性返回 java.lang.reflect.Method 对象 。
    • javaSetter:获取该属性的 setter 方法(如果该属性是读写属性的话) 。 该属性返回
      java.lang.reflect. Method对象。

    一旦获取了 Kotlin属性的幕后字段的 Field对象、getter和 setter方法的Method对象之后,剩下的就是 Java反射的事了。如下程序示范了通过 Kotiin属性来获取 Java反射 API。

    class Item3 {
        var name: String = "kotlin"
        var price = 16.1
    }
    
    var foo = "foo"
    
    fun main(args: Array<String>) {
        //获取 foo 属性,属于 KMutablePropertyO 的实例
        val topProp = ::foo
        println(topProp.javaField)
        //获取幕后字段
        println(topProp.javaGetter)
        //获取 getter方法
        println(topProp.javaSetter)
    
    
        //获取 Item3 的 name 属性,属于 KMutableProperty1 的实例
        val mp = Item3::name
        //获取幕后字段
        println(mp.javaField)
        //获取 getter 方法
        println(mp.javaGetter)
        //获取 setter方法
        println(mp.javaSetter)
    
    
        //获取 Item3 的 price 属性 ,属于 KMutableProperty1 的实例
        val prop = Item3::price
        //获取幕后字段
        println(prop.javaField)
        //获取 getter 方法
        println(prop.javaGetter)
    }
    

    绑定的方法与属性引用

    前面介绍的都是通过 KClass (类本身)来获取方法或属性的引用的,当函数或属性不在任何类中定义时,程序直接使用“::”加函数名(或属性名)的形式来获取函数或属性的引用, 这些函数或属性都没有绑定任何对象,因此调用函数或属性时第一个参数必须传入调用者。

    从 Kotlin 1.1 开始, Kotlin 支持一种“绑定的方法或属性引用”,这种方法或属性引用不是通过类获取的,而是通过对象获取的,这意味着该方法或属性己经绑定了调用者,因此程序执行这种方法或属性时无须传入调用者。
    如下程序示范了绑定的方法或属性引用。

    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)) //输出["Java","Go"]
        // 获取对象绑定的属性
        val prp = list::indices
        //调用绑定的属性时无须传入调用者
        println(prp .get()) //输出 0 .. 3
    }
    

    上面程序先定义了 一个简单的字符串,代码使用对象来获取绑定的方法, 这样当程序调用方法时只要传入该方法的两个参数即可,无须传入调用者;后面代码使用对象来获取绑定的属性,这样程序直接使用 get()方法即可获取绑定的属性值,无须传入调用者。

    相关文章

      网友评论

          本文标题:Kotlin和Java互相调用(二)

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