深入kotlin - 方法引用和属性引用
Posted 颐和园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入kotlin - 方法引用和属性引用相关的知识,希望对你有一定的参考价值。
反射
kotlin 通过 kotlin-reflect.jar 提供对反射的支持。
KClass
通过类引用 KClass
KClass 引用了 kotlin 类(具有内省能力)。类似于 Java 的 class 。要获取一个类的 KClass,通过类型名::class
获得,而对应的 Java class 则通过类型名::class.java
获得:
fun main(args: Array<String>)
val c= String::class // 获取 KClass
println(c) // 打印:class kotlin.String
val c2 = String::class.java // 获取 Java Class
println(c2) // 打印:class java.lang.String
通过实例引用 KClass
实际上和通过类引用一样,都是使用 :: 表达式,不过前半部分从类名变成了对象名。
open class Parent
class Son:Parent()
class Daughter: Parent()
fun main(args:Array<String>)
val son:Parent = Son()
val daughter: Parent = Daughter()
println(son::class) // 打印:class com.kotlin13.Son
println(son::class.java) // 打印:class com.kotlin13.son
println(daughter::class)
println(daughter::class.java)
可以看到虽然 对象声明时使用的是父类型,但它的 KClass 仍然是具体的子类型。此外,KClass 和 java class 的输出是一样的。
函数引用
fun addOne(x:Int): Int
return x+1
fun addOne(s:String):Int
return 10
fun main(args:Array<String>)
val values = listOf(1,3,5,7)
println(values.map(::addOne)) // 1.函数引用
val values2 = listOf("a","b","c")
println(values2.map(::addOne)) // 2.函数引用重载
-
Map 函数需要用另外一个函数作为参数。这里的 ::addOne 是一个方法引用(KFunction 类型,相当于 java 的 Method,具有内省能力的函数)。因为 addOne 是顶层函数,所以类名被省略了,直接以 :: 开始。
-
函数引用也有重载形式,因此两个 addOne 方法到底调用哪一个,kotlin 会通过类型推断来进行判断。对于 Int 数组,map 函数传递给 addOne 函数的参数应该是 Int,所以会调用第一个 addOne。对于 String 数组,map 函数会在每个 String 上应用 addOne 函数,因此只会调用第二个 addOne 函数。
这里,::addOne 真正类型其实是 (Int)->Int 或 (String)->Int。因此可以把一个函数引用赋给一个变量:
val ref1: (Int) -> Int = ::addOne // 引用第一个 addOne
val ref2: (String) -> Int = ::addOne // 引用第二个 addOne
val ref3:String.(Int)->Char = String::get // 引用 String 的 get(Int) 方法
值得注意的是,ref3 的声明类型。由于 String.get 不是一个顶层函数,所以类名前缀不可省略。
函数组合
即将两个函数组合成一个函数。
fun <A,B,C> myCompose(f:(B)->C, g:(A)->B): (A)->C // 一个范型函数,接收两个函数参数,返回一个函数
return x -> f(g(x)) // 返回一个函数
myCompose 将两个函数调用串联起来,即首先调用 g, 将一个 A 变成 B,然后调用 f,将 B 变成 C:
g: (A) -> B
f: (B) -> C
myCompose = g + f: (A) -> (B) -> C
这里, B 其实只是一个中间结果,是可以舍弃的,舍弃后就变成 myCompose: (A) -> C。
return x -> f(g(x)) 一句表示返回一个 lambda 表达式(函数),这个函数接收一个 x 参数(应该是一个 A 类型,和 g 的参数相同),然后返回 f(g(x)) 的结果(根据 f 的定义,返回的 将是一个 C 类型)。
因此我们查看 myCompose 的代码可知,这个函数其实是把两步调用合并成一个一步调用,原来需要先调用 B = g(A),再调用 C = f(B) 才能得到 C,现在变成了 C = myCompose(A)。
接下来我们可以利用这个函数,将获取字符串长度和判断长度是否为偶数到操作合并成一步操作:
fun isEven(x:Int) = 0==x%2 // 判断是否偶数
fun length(s:String) = s.length // 获取字符长度,它输出类型就是 isEven 的输入类型,因此可以将它的调用结果作为isEven 的参数
fun main(args: Array<String>)
val f = myCompose(::isEven, ::length) // 得到一个 (A) -> C 到函数
val strings = listOf("a","ab","abc")
println(strings.map(f)) // 输出 false, true, false
println(strings.filter(f)) // 输出 ab,长度为奇数的被过滤掉了
属性引用
形式与函数引用相同。实际上,:: 操作符不仅仅可以用于函数引用。
const val a = 3
fun main(args: Array<String>)
println(::a) // 打印属性类型: val a: koltin.Int
println(::a.get()) // 打印属性值: 3
println(::a.name) // 打印属性名:a
这里,::a 表示一个 KProperty 对象。通过这个 KProperty 访问指定属性的具体信息,相当于 java 的 Field。
KProperty 是一个接口,代表一个属性(val 或 var)。
注意,对于包级别的属性引用,使用的是 KProperty0 (继承自 KProperty -> KCallable)。
对于 var 属性,则使用 KMutableProperty。这个接口继承自 KProperty 但具有 set 方法。
var b = 5
fun main(args: Array<String>)
::b.set(10)
println(b) // 打印 10
println(::b.get()) // 打印 10
属性引用和函数引用很多时候是相同的,比如:
fun main(args: Array<String> )
val values = listOf("a","abc","abcd")
println(values.map(String::length)) // 引用 String.length属性,打印: 1,3,4
map 接收一个函数函数,我们应该将一个函数引用传递给它,但这里我们实际上传递了一个属性引用,这充分说明了属性引用和函数引用本质上是相同的。同时,map 会将数组中的元素传递函数引用,但如果是属性引用,map 会将元素传递给属性引用的接收者,即变成了 it.length,这会调用数组元素的 length 方法。
如果引用的属性不是包级别的,比如:
class MyClass(val x: Int)
fun main(args: Array<String>)
val x = MyClass::x
println(x.get(MyClass(10))) // 1 打印:10
- 如果属性属于具体类,那么所对应的属性引用必须依赖于具体的实例存在,因此 get 方法必须传递该类的一个实例。
通过实例获得函数/属性引用
fun main(args: Array<String>)
val str="abc"
val getRef = str::get // 通过对象而非类名来引用,对比 String::get
println(getRef(1)) // 打印:b
val propRef = "test"::length
println(propRef.get()) // 打印:4
注意,上述代码实际上等效于:
fun main(args: Array<String>)
val getRef = String::get // 通过类来引用,对比 str::get
println(getRef("abc", 1)) // 打印:b
val propRef = String::length
println(propRef.get("test")) // 打印:4
扩展属性
定义一个扩展属性:
val String.firstChar: Char
get() = this[0]
如何通过属性引用的方式打印这个 firstChar?
fun main(args:Array<String>)
val str = "xyz"
val x = String::firstChar
println(x.get(str)) // 打印:x
实际上和属性引用是一样的。
构造方法引用
引用形式仍然是 :: 表达式。区别仅在于,构造方法引用仅用于向参数和返回值与构造方法相同的变量赋值。
class MyClass(val x: Int)
fun method(factory: (x: Int) -> B) // factory 可以接受一个构造方法引用
val myClass:MyClass = factory(10)
println(myClass.x)
fun main(args: Array<String>)
method(::MyClass) // 向 method 传入一个构造方法引用
::MyClass 表示了构造方法:MyClass (val x: Int)。这个构造方法的签名和 factory 参数是一样的,接受一个 Int,返回一个 MyClass 对象。
类型参数
typeParameters 代表类型的范型参数列表:
class MyClass<K,V>
var k: K? = null
var v: V? = null
fun main(args: Array<String>)
val kc = MyClass::class
println(kc.typeParameters) // 打印:[K, V]
println(kc.typeParameters.size) // 打印:2
println(kc.typeParameters[0]) // 打印:K
以上是关于深入kotlin - 方法引用和属性引用的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin:: 双冒号操作符详解 ( 获取类的引用 | 获取对象类型的引用 | 获取函数的引用 | 获取属性的引用 | Java 中的 Class 与 Kotlin 中的 KClass )