Kotlin学习笔记7——高阶函数和Lambda表达式

前言

上一篇,我们学习了Kotlin中的普通函数,今天继续来学习Kotlin中的函数。由于Kotlin中支持高阶函数语法,所以函数我们分为三篇来学习,今天是第二篇:高阶函数和Lambda表达式。

高阶函数

高阶函数是将函数用作参数或返回值的函数。

函数用作函数参数

// sumBy函数的源码
public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
    
    
    var sum: Int = 0
    for (element in this) {
    
    
        sum += selector(element)
    }
    return sum
}
  • 首先inline和sumBy前的CharSequence这两个属于内联函数和拓展函数功能,可以先不用理会,后面会学习。
  • sumBy是一个接收参数为selector,返回值为Int类型的函数。其中参数selector是一个接收参数类型为Char,返回值为Int类型的函数。
  • 函数的意思就是传入一个函数,然后遍历字符串将每个元素转成Int类型累加后返回。

示例:

val testStr = "abc"
val sum = testStr.sumBy {
    
     it.toInt() }
println(sum)//返回结果294,因为字符a对应的值为97,b对应98,c对应99,故而该值即为 97 + 98 + 99 = 294

函数用作函数返回值

fun test(body: () -> Int): Int{
    
    
    return body()
}
  • lock函数接收两个参数,一个是Lock类型,另外一个是没有接收值,返回值为Int类型的函数。
  • try代码块中将body函数进行返回。

示例:

val testResult = Test().test {
    
     'a'.toInt() }
println("result : $testResult")
//结果是result : 97

自定义高阶函数

传入两个参数,并传入一个函数来实现他们不同的运算逻辑

private fun mathByFun(num1 : Int , num2 : Int , result : (Int ,Int) -> Int) : Int{
    
    
    return result(num1,num2)
}
private fun mathOpt() {
    
    
	//按照mathByFun函数中的参数顺序应该是下面的写法,但是Lambda表达式作为最后一个参数的时候可以挪出括号外,这里说明下
	/*val resultAdd = mathByFun(1,2, {
                num1, num2 ->  num1 + num2
        })*/
    val resultAdd = mathByFun(1,2){
    
    
        num1, num2 ->  num1 + num2
    }
    val resultSub = mathByFun(3,4){
    
    
        num1, num2 ->  num1 - num2
    }
    val resultMul = mathByFun(5,6){
    
    
        num1, num2 ->  num1 * num2
    }
    val resultDiv = mathByFun(6,3){
    
    
        num1, num2 ->  num1 / num2
    }
    println("resultAdd = $resultAdd")
    println("resultSub = $resultSub")
    println("resultMul = $resultMul")
    println("resultDiv = $resultDiv")
}

可以看到在mathOpt函数中,调用mathByFun函数,传入不同函数体从而实现了不同的运算逻辑。

Lambda 表达式与匿名函数

lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递。考虑下面的例子:

max(strings, {
    
     a, b -> a.length < b.length })

函数 max 是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值,它等价于以下具名函数:

fun compare(a: String, b: String): Boolean = a.length < b.length

Lambda 表达式语法

Lambda 表达式的完整语法形式如下:

val sum: (Int, Int) -> Int = {
    
     x: Int, y: Int -> x + y }

lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个) 表达式会视为返回值。

如果我们把所有可选标注都留下,看起来如下:

val sum = {
    
     x: Int, y: Int -> x + y }

拖尾 lambda 表达式

在 Kotlin 中有一个约定:如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外(这个在上面的例子中也有提过):

val product = items.fold(1) {
    
     acc, e -> acc * e }

如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:

run {
    
     println("...") }

it:单个参数的隐式名称

一个 lambda 表达式只有一个参数是很常见的。
如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->。 该参数会隐式声明为 it:

val ints = listOf(1,2,3,-1)
ints.filter {
    
     it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的

lambda 表达式中返回一个值

我们可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。
因此,以下两个片段是等价的:

ints.filter {
    
    
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    
    
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

下划线用于未使用的变量

如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称:

//这里没有用到num1这个参数,可以用_来替代
val resultAdd = mathByFun(1,2) {
    
    
    _, num2 ->  num2
}

匿名函数

上面提供的 lambda 表达式语法缺少的一个东西是指定函数的返回类型的能力。在大多数情况下,这是不必要的。因为返回类型可以自动推断出来。然而,如果确实需要显式指定,可以使用另一种语法: 匿名函数 。

fun(x: Int, y: Int): Int = x + y

匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体可以是表达式(如上所示)或代码块:

fun(x: Int, y: Int): Int {
    
    
    return x + y
}

参数和返回类型的指定方式与常规函数相同,除了能够从上下文推断出的参数类型可以省略:

ints.filter(fun(item) = item > 0)

匿名函数的返回类型推断机制与正常函数一样:对于具有表达式函数体的匿名函数将自动推断返回类型,而具有代码块函数体的返回类型必须显式指定(或者已假定为 Unit)。

请注意,匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。

Lambda表达式与匿名函数之间的另一个区别是非局部返回的行为。一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。

闭包

我们都知道,程序的变量分为全局变量和局部变量,全局变量,顾名思义,其作用域是当前文件甚至文件外的所有地方;而局部变量,我们只能再其有限的作用域里获取。
那么,如何在外部调用局部变量呢?答案就是——闭包,与此给闭包下个定义:闭包就是能够读取其他函数内部变量的函数

  • 它是运行的环境
  • 它持有函数的运行状态
  • 它的内部可以定义函数
  • 它的内部也可以定义类

看示例:

fun makeFun():()->Unit{
    
    
        var conut = 0
        return fun(){
    
       //返回一个匿名函数,这个函数持有count的状态
            println(++conut)
            //可以定义函数
            //fun test1(){}
            //可以定义类
            //class Test2{}
        }
    }
fun main(args: Array<String>) {
    
    
    val makeFun = makeFun() //函数调用,返回一个函数
    makeFun()       //调用这个返回的函数,此时makeFun持有makeFun()内部变量的状态
    makeFun()
    makeFun()
}
//返回结果1 2 3

回调函数的lambda简化

1、用Java代码实现一个接口的回调

obj.setEventListener(new EventListener(){
    
    
	public void callback(Data obj){
    
    
		//todo
	}
});

2、Kotlin中使用object关键字实现回调

obj.setEventListener(object: EventListener{
    
    
	public void callback(Data obj){
    
    
		//todo
	}
})

3、如果在Kotlin中的对于接口只有一个回调的方法,就符合使用lambda函数,我们可以把以上代码简化成这样。

obj.setEventListener({
    
    
   data: Data ->
   //todo
})

//或者可以直接省略Data,借助kotlin的智能类型推导

obj.setEventListener({
    
    
   data ->
   //todo
})

4、如果以上代码中的data参数没有使用到的话,可以直接把data去掉

obj.setEventListener({
    
    
  //todo

})

5、由于setEventListener函数最后一个参数是一个函数的话,可以直接把括号的实现提到圆括号外面

obj.setEventListener(){
    
    
   //todo
}

6、由于setEventListener这个函数只有一个参数,可以直接省略圆括号

obj.setEventListener{
    
    
  //todo
}

尾巴

今天的学习笔记就先到这里了,下一篇我们将学习Kotlin中的内联函数
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!

猜你喜欢

转载自blog.csdn.net/abs625/article/details/106884843