Lambda闭包

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

Lambda闭包

kotlin



Lambda表达式的语法:

在Kotlin中,Lambda表达式的语法如下:

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

Lambda表达式始终是被{}包围起来的,花括号里面包含了三个部分:参数,函数体和箭头。下面我们通过一个例子详细的了解下Lambda表达式的写法和参数的意义。

在“https://www.jianshu.com/p/816fa66badc0”笔记的操作符中,我们提到了Lambda表达式,但是没有做详细说明,现在我们还是通过那个例子来加以说明。代码如下:

 1package lambda
2
3data class Person(val age: Intval name: String)
4
5fun learnLambda() {
6    val list = listOf(Person(22"tao"), Person(23"Kotlin"), Person(11"Java"))
7    print(list.filter({ p: Person -> p.age % 2 == 0 }))
8}
9
10fun main(args: Array<String>) {
11    learnLambda()
12}
13
14// 输出:[Person(age=22, name=tao)]

filter操作符后面就是一个很经典的Lambda表达式,作用是取出集合中年龄除以2取余等于0的项。通过list.filter({ p: Person -> p.age % 2 == 0 })这句代码就能清楚的了解Lambda的整体结构,。但是我们会发现,其实这种写法还是很繁琐,使用Lambda表达式就是为了简化代码,便于理解,这种并没有达到我们想要的目的。接着我们就来一步步的按照Kotlin指定的规则来简化。

Lambda表达式括号的简化:

  1. 在Kotlin实战这本书籍中提到了,Kotlin有这样一种语法约定,如果Lambda表达式是函数调用的最后一个参数,它可以放到括号外面,在上面的例子中,{ p: Person -> p.age % 2 == 0 }Lambda表达式是filter()函数的唯一参数,所以可以放到括号外面,写法为:
    list.filter() { p: Person -> p.age % 2 == 0 }

  2. 当Lambda表达式是函数唯一的实参时,还可以去掉调用代码中的空括号对,写法为:
    list.filter { p: Person -> p.age % 2 == 0 }

这里你需要注意下,当函数存在两个或两个以上Lambda实参时,就不能把所有的Lambda表达式都放在括号外面了,这时候你应该使用常规的语法。

Lambda表达式参数的简化:

通过上面两部的操作,在只存在一个Lambda表达式作为实参时,我们可以去除函数的括号,下面我们来继续优化函数实参:

  1. 对面list.filter { p: Person -> p.age % 2 == 0 }函数来说,实参只有一个p,而且其类型始终和集合list元素的类型相同,编译器知道你是对一个Person对象的集合调用filter函数,所有编译器能推断Lambda表达式的参数会是Person类型。这时候我们就可以不用声明参数的类型,写法如下:
    list.filter { p -> p.age % 2 == 0 }

  2. 通过上面这步,大致已经简化到了最优的了,这时候大家可以通过编译器的智能推荐功能(Windows:Alt+enter;Mac:option+enter)看到,其实还有可以简化的地方,编译器推荐采用下面的方法:
    list.filter { it.age % 2 == 0 }
    其实这种写法是建立在Lambda表达式只有一个参数,并且这个参数的类型是可以被推导出来的,才会生成it这个名称。it就是代替了我们声明的参数。

使用it虽然可以很大的简化你的代码,但是切记不可滥用,目前笔者只是用一个Lambda表达式作为示例,如果存在嵌套的Lambda,即使只有Lambda只有一个参数,最好也不要使用it,这样很容易弄混这个it引用的是哪个值,推荐在嵌套的Lambda时,显示的声明每个Lambda的参数。


成员引用:

1val getAge = Person::age

这种表达式就称之为成员引用,它提供了一种很简明的语法,来创建一个调用单个方法或者访问单个属性的函数值。双冒号把类名与引用的成员(一个方法或者一个属性)名称隔开。

再来看看和Lambda表达式写法有啥区别:

1    val list = listOf(Person(22"tao"), Person(23"Kotlin"), Person(11"Java"))
2    println(list.maxBy { it.age })
3    println(list.maxBy(Person::age))

看到这两种写法是不是有小伙伴要问了,这不Lambda表达式更简洁么?没错,在没有嵌套Lambda的时候当然不存在优势之分了,别着急,接下来我们慢慢看、慢慢学。

构造方法引用:

上代码:

1    // 构造方法引用:
2    val getPerson = ::Person
3    println(getPerson(11"taonce"))

我们用一个变量值将创建Person实例的动作保存了,后面只需调用这个变量就ok了。

扩展函数的引用:

上代码:

1data class Person(val age: Intval name: String)
2// 这是Person的一个扩展函数,判断是否成年
3fun Person.isAdult() = age >= 18
1// 扩展函数引用:
2val adult = Person::isAdult
3val child = getPerson(11"taonce")
4println(adult(child))
5
6// 输出:false

上面介绍了几种引用之后,现在到了该大显身手的时候了,在上文中我提过,如果存在嵌套的Lambda,即使只有Lambda只有一个参数,最好也不要使用it,这样很容易弄混这个it引用的是哪个值,推荐在嵌套的Lambda时,显示的声明每个Lambda的参数。这是在笔记六中原话,现在我么你想要实现一个功能,输出年龄最大的人的信息,你试试用两个it来表达,编译器变回发出警告,这时候我们就可以用成员引用来轻松的解决了:

1    // 输出年龄超过30岁的人的姓名
2    val people = listOf(Person(22"A"),
3            Person(32"B"),
4            Person(11"C"),
5            Person(40"D"))
6    println(people.filter { it.age == people.maxBy { it.age }!!.age })
7    println(people.filter { it.age == people.maxBy(Person::age)!!.age })

我写了两种,第一种是Lambda,第二种是引用,但是第一种编译器会发出警报,说你的it指向不明,经过编译器的改正写法为:
println(people.filter { it -> it.age == people.maxBy { it.age }!!.age }),反而变得复杂,所以这时候的引用就发挥了很大的作用。

引用在越复杂的代码中起到的作用越来,希望在自己的练手demo中好好领悟这一点。


写在最后

每个人不是天生就强大,你若不努力,如何证明自己,加油!

Thank You!

--Taonce

如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~

Lambda闭包


原文始发于微信公众号(Taonce):Lambda闭包