kotlinKotlinAndroid知识

Kotlin学习笔记(14)- lambda

2017-07-02  本文已影响931人  我爱吃栗子啊

系列文章全部为本人的学习笔记,若有任何不妥之处,随时欢迎拍砖指正。如果你觉得我的文章对你有用,欢迎关注我,我们一起学习进步!
Kotlin学习笔记(1)- 环境配置
Kotlin学习笔记(2)- 空安全
Kotlin学习笔记(3)- 语法
Kotlin学习笔记(4)- 流程控制
Kotlin学习笔记(5)- 类
Kotlin学习笔记(6)- 属性
Kotlin学习笔记(7)- 接口
Kotlin学习笔记(8)- 扩展
Kotlin学习笔记(8)- 扩展(续)
Kotlin学习笔记(9)- 数据类
Kotlin学习笔记(10)- 泛型
Kotlin学习笔记(11)- 内部类和嵌套类
Kotlin学习笔记(12)- 委托
Kotlin学习笔记(13)- 函数式编程
Kotlin学习笔记(14)- lambda

在上一篇《Kotlin学习笔记(13)- 函数式编程》的最后我提到lambda为函数式编程提供了更多更好的实现,如果你还不太了解什么是函数式编程,那么可以看看我的上一篇文章。那么我们首先来看看什么是lambda,在百度百科上是这么说的:

“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

一、Lambda的定义

看上面的说法有点抽象,有点不明所以,我们先来看一个栗子

class Num {
    fun logic(a: Int, b: Int, calc: (Int, Int) -> Int){
        println("calc : ${calc(a,b)}")
    }
}

fun main(args : Array<String>){
    val num = Num()
    num.logic(1, 2, {x,y -> x+y})
}

// 输出
calc : 3

这个栗子和上一篇文章很像,只是在调用的时候改成了Lambda方式:num.logic(1, 2, {x,y -> x+y}),其中{x,y -> x+y}就是我们今天要讲的Lambda表达式,它的完整格式应该是这样

{ x: Int, y: Int -> x + y }

写成java代码是这个样子:

public int sum(int x, int y){
    return x+y;
}

可以很明显看出有几个规则:

  1. 参数写在->左边,格式与普通函数的参数格式一样,多个参数用逗号,分割
  2. 参数的类型可选,可忽略,编辑器会根据上下文推断(这就和普通函数不一样了吧,有种“我知道你懂得,所以就不写了”的感觉有木有,知己的感觉啊……)
  3. 函数体跟在->右边
  4. Lambda表达式总是被大括号{}包围着

二、Lambda中的一些约定

  1. 如果 Kotlin 可以自己计算出签名,它允许我们不声明唯一的参数,并且将隐含地为我们声明其名称为 it。其实通常情况下它都可以自己计算出签名,也就是说,如果函数字面值只有一个参数, 那么它的声明可以省略(连同 ->),其名称是 it

    fun oneParams(one : (Int) -> Int){
        println("oneParams : ${one(5)}")
    }
    
    fun main(args : Array<String>){
        val num = Num()
        num.oneParams({it * 2})
    }
    
    // 输出
    oneParams : 10
    

    说到省略,其实还有一种情况可以省略->,大家应该也能想到,就是无参函数。

    fun empty(emptyM : () -> Unit){
        emptyM()
    }
    
    fun main(args : Array<String>){
        val num = Num()
        num.empty({println("empty method")})
    }
    
    // 输出
    empty method
    
  2. 如果Lambda中的某个参数没有用到,可以用下划线_代替,也就是说,省了好多请名字的脑细胞有木有!这个特性从1.1开始可以使用,现在你看到的时候应该已经不止1.1了吧,所以这个限制看看就好~

    fun unusedParams(unused : (Int,Int) -> Int){
        println("unusedParams : ${unused(5,10)}")
    }
    
    fun main(args : Array<String>){
        val num = Num()
        num.unusedParams { _, used -> used * 2 }
    }
    
    // 输出
    unusedParams : 20
    
  3. 如果函数的最后一个参数是一个函数,那么我们在用Lambda表达最后一个函数参数的时候,可以把它放在括号()外面,所以下面的写法是等价的。

    class Num {
        fun logic(a: Int, b: Int, calc: (Int, Int) -> Int){
            println("calc : ${calc(a,b)}")
        }
        fun sum(a: Int, b: Int) = a + b
    }
    
    fun main(args : Array<String>){
        val num = Num()
        // 写法1
        num.logic(1, 2, {x : Int,y : Int -> x+y})
        // 写法2
        num.logic(1, 2){x : Int,y : Int -> x+y}
        // 写法3
        num.logic(1, 2){x,y -> x+y}
    }
    

    那么这么写有什么好处呢?难道只是位置变了一下?当然不是,不要忘记,Lambda的->后面是方法体,也就是很多时候不是想栗子中这样只有一行,如果有多行的话,体会一下他们的区别:

    num.logic(1, 2, {x,y ->
        println("extra line")
        x+y
    })
    
    num.logic(1, 2){x,y ->
        println("extra line")
        x+y
    }
    

    是不是感觉下面的写法要优雅很多,也明确很多?

  4. 其实在上面一点应该已经能看出,如果有需要的话,Lambda会隐式的返回最后一个表达式的值,就像上面的最后一行x+y。当然,我们也可以显示的表达返回值,下面的写法还是一样的:

    // 写法1
    num.unusedParams { _, used ->
        println("print first")
        return@unusedParams used * 2
    }
    
    // 写法2
    num.unusedParams { _, used ->
        println("print first")
        used * 2
    }
    

三、匿名函数

看了这么多,我们发现一个问题,Lambda的返回类型全是自动推断的,虽然很人性化,但是有时候我们就是想自己指定类型怎么办?当然是有办法的,那就是匿名函数

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

匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体可以是表达式(如上所示)或代码块。由于这已经不算正经的Lambda,所以它不需要被大括号{}包裹,也不能像Lambda一样写在括号()外,而调用也是直接调用。

num.logic(1, 2, fun(x:Int, y:Int):Int{return x+y})

四、闭包

Lambda 表达式或者匿名函数(以及局部函数对象表达式) 可以访问其 闭包 ,即在外部作用域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的变量:

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

五、小结

其实没什么小结,关于更多的函数式的高级应用、复杂应用,个人也正在学习,大家互相交流。

上一篇下一篇

猜你喜欢

热点阅读