深入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.函数引用重载

  1. Map 函数需要用另外一个函数作为参数。这里的 ::addOne 是一个方法引用(KFunction 类型,相当于 java 的 Method,具有内省能力的函数)。因为 addOne 是顶层函数,所以类名被省略了,直接以 :: 开始。

  2. 函数引用也有重载形式,因此两个 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
  
  

  1. 如果属性属于具体类,那么所对应的属性引用必须依赖于具体的实例存在,因此 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 - KClass 特性深入研究

深入kotlin - KClass 特性深入研究

深入kotlin - KClass 特性深入研究

Kotlin:: 双冒号操作符详解 ( 获取类的引用 | 获取对象类型的引用 | 获取函数的引用 | 获取属性的引用 | Java 中的 Class 与 Kotlin 中的 KClass )

Proguard 和 Kotlin 的“找不到引用的类”

深入Java垃圾收集