5.1、Lambda表达式和成员引用
lambda表达式,简称lambda,本质上就是可以传递给其他函数的一小段代码,有了lambda,可以轻松的把通用的代码结构抽取成库函数,最常见的一种Lambda用途就是和集合一起工作。
1、Lambda简介:作为函数参数的代码块
-
在代码中存储和传递一小段行为是常有的任务,例如:当一个事件发生的时候,运行这个事件处理器(比如点击事件),在老版本的Java中,可以用匿名内部类来实现,这种技巧可以工作但是语法太啰嗦了
-
函数式编程提供了另外一种解决问题的方法:把函数当作值来对待。可以直接传递函数,而不需要先声明一个类再传递这个类的实例
-
例子
2、Lambda和集合
-
良好编程风格的主要原则之一是避免代码中的任何重复
-
在没有lambda之前,对集合的操作,已经养成了什么东西都要自己实现的习惯,在Kotlin中这个习惯必须纠正
-
例子
-
Person类,它包含了这个人的名字和年龄信息
-
假设现在有一个人的列表,需要找到列表中年龄最大的那个人
-
如果完全不了解lambda,需要手动实现这个搜索功能,你可能需要先引入两个中间变量,一个用来保存最大的年龄,而另一个用来保存找到的此年龄的第一个人,然后迭代这个列表,不断更新这两个变量
-
手动搜索实现
-
使用库函数maxBy实现
-
maxBy函数可以在任何集合上调用,且只需要一个实参:一个函数,指定比较哪个值来找到最大元素。花括号的代码{it.age}就是实现了这个逻辑的lambda。它接收一个集合中的元素作为实参(使用it引用它)并且返回用来比较的值
-
这个例子中,集合元素是Person对象,用来比较的是存储在其age属性中的年龄
-
如果lambda刚好是函数或者属性的委托,可以用成员引用个替换
-
3、Lambda表达式的语法
-
一个lambda把一小段行为进行编码,你能把它当作值到处传递。它可以被独立地声明并存储到一个变量中,但是更常见的还是直接声明它并传递给函数。
-
Lambda表达式的语法
- Kotlin的lambda表达式始终用花括号包围;
注意实参并没有用括号括起来;箭头把实参列表和lambda的函数体隔开
- Kotlin的lambda表达式始终用花括号包围;
-
可以把lambda表达式存储在一个变量中,把这个变量当做普通函数对待(即通过相应的实参调用它)
-
还可以直接调用lambda表达式:
-
但是这样的语法毫无可读性,也没有什么意义(它等价于直接执行lambda函数体中的代码)
-
如果确实需要把一小段代码封闭在一个代码块中,可以使用库函数run来执行它的lambda
-
查找年龄最大的代码,如果不适用任何简明语法来重写这个例子,代码如下
-
这段代码一目了然:花括号的代码片段是lambda表达式,把它作为实参传给函数,这个lambda接收一个类型为Person的参数并返回它的年龄
-
但是这段代码有点啰嗦。首先,过多的标点符号破坏了可读性。其次,类型可以从上下文推断出来并可以省略。最后,这种情况下不需要给lambda的参数分配一个名称
-
让我们来改进这些地方,先拿花括号开刀
-
Kotlin有这样一种语法约定,如果lambda表达式是函数调用的【最后一个实参】,lambda可以放到括号的外边
-
在这个例子中,lambda是【唯一的实参】,所以可以放到括号的后边
-
当lambda是函数唯一的实参时,可以去掉调用代码中的空括号对
-
三种语法形式含义都是一样的,但最后一种最易读。如果lambda是唯一的实参,可以省略掉括号,而当有多个实参时,既可以把lambda留在括号内来强调它是一个实参,也可以把它放在括号的外面,两种选择都是可行的
-
如果想传递两个或更多的lambda,不能把超过一个的lambda放到外面,这时使用常规语法来传递它们通常是更好的选择
-
省略lambda参数类型,和局部变量一样,如果lambda参数的类型可以被推导出来,就不需要显式地指定它
-
这里maxBy函数为例,其参数类型始终和集合的元素类型相同,编译器知道你是对一个Person对象的集合调用maxBy函数,所以它能推断lambda参数也会是Person类型
-
也存在编译器不能推断lambda参数类型的情况,另行讨论。可以遵循这样一条简单的规则:先不声明类型,等编译器报错后再指定他们
-
可以指定部分实参的类型,而剩下的实参只用名称。如果编译器不能推导出其中一种类型,又或是显式的类型可以提升可读性,这种做法或许更方便
-
最后简化,使用默认参数名称it代替命名参数。如果当前上下文期望的只有一个参数的lambda且这个参数的类型可以推断出来,就会生成这个名称
- 代码简化过程
- 代码简化过程
-
仅在实参名称没有显式地指定时这个默认的名称才会生成
-
注意
- it约定能大大缩短代码,但不应该滥用它。尤其是在嵌套lambda的情况下,最好显式地声明每个lambda的参数。否则很难搞清楚it引用的到底是哪个值。如果上下文中参数的类型或意义都不是很明朗,显式声明参数的方法也很有效
-
如果用变量存储lambda,那么就没有可以推断出参数类型的上下文,所以必须显示地指定参数类型
-
迄今为止,例子都是由单个表达式或语句构成的lambda,其实它可以包含更多的语句
-
复杂调用,回顾joinToString函数,Kotlin标准库中也有定义,不过,它可以接收一个附加的函数参数,这个函数可以用toString函数以外的方法来把一个元素转换成字符串
-
例子
- 只打印人的名字
- 重写这个调用,把lambda放在括号外
-
4、在作用域中访问变量
-
和lambda表达式形影不离的概念:从上下文中捕捉变量
-
当在函数内声明一个匿名内部类的时候,能够在这个匿名类内部引用这个函数的参数和局部变量
-
lambda可以做同样的事情,如果在函数内部使用lambda,也可以访问这个函数的参数