高阶函数是将函数用作参数或返回值的函数。
如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数称为高阶函数
函数类型定义的基本规则:
/* methodName函数名
* (Int,String) 函数的参数类型
* Unit 函数返回值 === void
*/
methodName : (Int,String) -> Unit
将函数添加到另外一个函数的参数中:
fun test(block : (String , Int) -> Unit) {
block("test",123)
}
这样test函数就是一个高阶函数了,调用的时候:
test { name, age ->
println("name:$name,age:$age")
}
// 打印结果
name:test,age:123
其中,test后的大括号括起来的内容,都是block函数的实现
再举一个带参数的例子:
// 最后一个Unit代表test2函数的返回值,可以是Int,Stirng等等,这样就需要再方法中实现返回值
fun test2(string: String, example: (String) -> Unit): Unit {
example(string)// 将string传递给example作为参数
}
// 调用
test2("TEST") {
// it是lambda隐藏了参数名,也可以自定义参数名,一般默认不写就是it
println("it : $it")
}
// 自己写了参数名
test2("TEST2") {name->
println("name : $name")
}
// 打印结果
it : TEST
name : TEST2
高阶函数允许让函数类型参数决定函数的执行逻辑,即使同一个高阶函数,传入的函数类型参数不同(也可以相同,主要取决于高阶函数的具体实现也就是{}中的代码),那么函数的执行逻辑和返回结果可能也是完全不同的。
带参数和返回值的高阶函数(传入相同参数,不同实现方法):
fun test3(a: Int, b: Int, num: (Int, Int) -> Int): Int {
return num(a, b)
}
// 调用,在{}中的实现方法可以自己写,可以两数相加,也可以相乘,相除,相减等等
val addNum = test3(1, 2) { a, b ->
a + b
}
val rideNum = test3(1, 2) { a, b ->
println("函数作为参数的实现方法:a : $a , b : $b")
a * b // lambda最后一行作为高阶函数的返回值返回
}
// ::addNum 这是一种函数引用的写法,表示将函数addNum()来作为参数传递给高阶函数
val addNum2 = test3(1, 2, ::addNum)
fun addNum(a: Int, b: Int): Int {
return a + b
}
println("addNum : $addNum")
println("rideNum : $rideNum")
println("addNum2 : $addNum2")
// 打印结果
addNum : 3
rideNum : 2
addNum2 : 3
其中出现了一种新的写法::addNum
,这是一种函数引用写法,表示将函数addNum()
作为参数传递给高阶函数。
如果函数在另外一个类中定义,假如在Test类中,有addNum()方法,则需要先创建类的对象,然后用test::addNum
作为高阶函数的参数
val test : Test = Test()
test3(1, 2, test::addNum())
从rideNum
中可以看到,Lambda表达式的最后一行代码的返回值作为函数的返回值返回。
apply函数扩展了所有的泛型对象,在闭包范围内可以任意调用该对象的任意方法,并在最后返回该对象。
主要的作用:是可以用来简化初始化对象的功能
特别需要注意的是apply函数中表示对象本身使用的是this关键字而不是it
val stringBuilder = StringBuilder("000")
str
// apply 方法返回this,也就是 StringBuilder,即常量 s 是 StringBuilder类型
val s = stringBuilder.apply {
append("aaa")// === stringBuilder.append("aaa") 省略了this
append("bbb")
}
println("S == $s")
// 打印结果
S == 000aaabbb
我们来看一下apply函数的源码:
意思是,为泛型T增加扩展函数apply
,返回值为泛型本身类型,使用 T.()
表示在T类中定义的函数类型,那么在传入Lambda表达式时将会自动拥有T的上下文 this
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
为StringBuilder
增加扩展函数myApply
,扩展函数接收一个函数类型的参数,并且返回值为StringBuilder
。
这里和前面的函数作为参数有不一样的地方,即在括号前加了
StringBuilder.
,其实这才是完整的函数类型的定义规则,加上T.
表示在哪个类中定义函数类型。使用StringBuilder.
表示在StringBuilder类中定义的函数类型,那么在传入Lambda表达式时将会自动拥有StringBuilder的上下文this。
// 为StringBuilder增加扩展函数myApply
fun StringBuilder.myApply(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this// this 就代表.()前面的StringBuilder
}
// 调用
val s2 = stringBuilder.myApply {
this.append("ccc") // === append("ccc")
}
println("s2 == $s2") // 000aaabbbccc
图中框起来的部分this:StirngBuilder
,即为自动拥有的上下文,也就是stringBuilder
常量(此处有个问题,stringBuilder定义的是val常量,为什么可以通过append修改?查了一下和基本数据类型与引用数据类型有关,后续研究)
自己定义的方法,因为写死了StringBuilder,可以替换成泛型,就和源码一样了,不过此处少了inline
修饰
fun <T> T.myApply2(block: T.() -> Unit): T {
block()
return this
}
内联函数的原理也很简单:Kotlin编译器在编译时把内联函数内代码自动替换到要调用的地方,这样就解决了运行时的内存开销
val rideNum = a + b
正是如此内联函数才能完全消除Lambda表达式运行时带来的额外内存开销