- lambda 本质是可以传递给其他函数的一小段代码。
5.1 Lambda 表达式和成员引用
- 函数式编程与 lambda 表达式:函数式编程是把函数当做值来对待可以直接传递函数;lambda 表达式使得代码更简洁,不需要声明函数,可以高效地直接传递代码块作为函数参数。
button.setOnClickListener {/* 点击后的执行动作 */} // 注意是大括号
-
Lambda 语法:
{ x: Int, y: Int -> x + y}
- 其中 -> 前的部分为参数,后面部分为函数体。注意到实参并没有用括号括起来,实参和函数体使用了 -> 符号隔开。
- 可以把 lambda 表达式存储在一个变量中,这个变量当做普通函数对待
-
看一下 lambda 表达式简写的演变过程:
// 前期准备
val people = listOf(Person("Alice", 29), Person("Bob", 31))
// maxBy 方法的系统声明
fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T?
// 原始方式
people.maxBy({ p: Person -> p.age })
// lambda 表达式是函数调用的最后一个实参,可放到括号外面
people.maxBy(){ p: Person -> p.age }
// lambda 是函数唯一实参,可以去掉空括号对
people.maxBy { p: Person -> p.age }
// 与局部变量一样,如果 lambda 参数类型可以被推到,就可以省略类型
// 也存在不能被推到的情况,可以遵循这样的规则:先不声明类型,编译器报错后再声明
people.maxBy {p -> p.age}
// 当只有一个参数并且参数类型可以推导,就可以使用默认参数名称 it 代替命名参数
people.maxBy { it.age }
-
当实参有多个 lambda 时,不能把超过一个 lambda 放到外面,所以都放在 () 倒是更好的选择
-
当在函数内声明一个匿名 内部类的时候,能够在这个匿名类内部引这个函数的参数和局部变量。 也可以用 lambda 做同样的事情 。 如果在函数内部使用 lambda,也可以访问这个外部函数的参数以及在 lambda之前定义的局部变量。
- 与 Java 不同的是,Kotlin 中不会仅限于访问 final 变量,在 lambda 内部也可以修改这些变量。
- 从 lambda 内访问外部变量,称这些变量被 lambda 捕捉
-
成员引用:Person:: age,类后(比如此处的 age)可以是函数也可以是属性,并且都无需加 ()。这样对于已经定义好的函数,也可以方便的作为参数传递。
5.2 集合的函数式API
- 函数式编程风格在操作集合时提供了很多优势,kotlin 也加入了不少库函数来帮助解决集合问题。
- filter :遍历集合并选出应用给定 lambda 后返回 true 的那些元素(即选出符合条件(lambda 函数)的集合元素,组成新集合)
- map:对集合中每个元素应用给定的 lambda 并把结果收集到一个新集合;
- all:集合中是否所有元素都符合某条件(lambda),返回值是 boolean 类型;
- any:集合中是否存在元素符合某条件,同样返回 Boolean 类型;
- find:返回第一个符合条件的元素;
- count:返回符合条件的元素总个数;
- groupBy:把集合元素按照某特征(lambda)划分成不同的分组,返回是一个 map,key 为 lambda 中的条件,value 是列表集合中的元素;
- flatMap:把结合中所有元素按照 lambda 做变化,然后想得到的结果“平铺”,返回平铺后的集合;
- 注意:使用 lambda 表达式的代码看起来简单,有时候却掩盖了底层操作的复杂性,很容易写出不必要的重复计算的逻辑,尤其是对于集合的操作,产生不必要的循环或重复。所以始终要牢记你写的代码在干什么!
5.3 惰性集合操作:序列
-
上面那些处理链表的函数,在链式调用时往往每一步都会创建新的链表,当处理大数据量时,效率较低。使用序列可以避免创建这些临时中间对象。
-
Kotlin 惰性集合操作的入口就是 Sequence 接口 。这个接口表示的就是一个可以逐个列举元素的元素序列。 Sequence 只提供了一 个方法 iterator,用来从 序列中获取值。Sequence 接口的强大之处在于其操作的实现方式 。序列中的元素求值是惰性的。因此,可以使用序列更高效地对集合元素执行链式操作,而不需要创建额外的集合来保存过程中产生的中间结果。可以调用扩展函数 asSequence 把任意集合转换成序列,调用 toList 来做反向转换。
listOf(1,2,3,4).asSequence().map{ it*it }.filter{ it>3 }.toList()
-
这种操作其实主要分两步:中间操作和末端操作。中间操作始终都是惰性的,末端操作(toList() )触发执行了所有的延期计算。
-
原理是“及早求值”,也就是会把序列中的元素,依次处理所有过程,这样有可能省去部分处理过程。比如我们把上例最后的 filter 变为 find,那么如果是对集合处理,会先所有元素求平方,再找第一个大于 3 的元素,而对于序列处理的话,会从第一个元素开始,先求平方,再看结果是否大于 3,如此找到第一个大于 3 的值就宣告结束了。
5.4 使用 Java 函数式接口
-
Kotlin 的 lambda 可以无缝地和 Java Api 互操作。
-
把只含有一个方法的接口成为函数式接口,Android 中 OnClickListener,java 中比如 Runnable Callable 等都是函数式接口,Kotlin 允许你在调用接收函数式接口作为参数的方法时使用 lambda。
// java 中的声明 public class View{ public void setOnClickListener(OnClickListener l){...} } // Kotlin 调用 button.setOnClickListener{...}
-
参数可以有多个,只要含有函数式接口类型的参数,就可以使用 lambda
-
实现原理:在 kotlin 中,每个 lambda 表达式都会被编译成一个匿名类,如果 lambda 捕捉了变量,每个被捕捉的变量会在匿名类中有对应的字段。
5.5 带接收者的 lambda:with 与 apply
-
With:可以用它对同一个对象执行多次操作,而无需反复把对象的名称写出来。
-
fun alphabet()= with(StringBuilder()){ for (letter in 'A'..'Z'){ append(letter) } toString() }
- with 实际上是一个接收两个参数的函数,这个例子中两个参数是 StringBuilder 对象和一个 lambda,这里利用了把最后的 lambda 放到括号外的约定,这样看起来更像是内建的语言功能。并且示例代码中 lambda 内部省略了 this 引用。
-
apply:和 with 很像,但 apply 始终返回作为实参传递给它的对象。
fun alphabet()= StringBuilder().apply { for (letter in 'A'..'Z'){ append(letter) } }.toString()
-
二者区别:
- 显然,with 是库函数,apply 是扩展函数
- with 返回的是最后一行表达式(的值),apply 返回的是apply 其实是实参传进来的对象(接收者对象)。
5.6 小结
- Lambda 允许你把代码块当作参数传递给函数。
- Kotlin 可以把 lambda 放在括号外传递给函数,而且可以用 it 引用单个的 lambda 参数。
- lambda 中的代码可以访问和修改包含这个 lambda 调用的函数中的变量。
- 通过在函数名称前加上前缀 ::,可以创建方法、构造方法及属性的引用,并用这些引用代替 lambda传递给函数。
- 使用像 filter、map、all、any 等函数,大多数公共的集合操作不需要手动迭代元素就可以完成。
- 序列允许你合并一个集合上的多次操作 ,而不需要创建新的集合来保存中间结果。
- 可以把 lambda 作为实参传给接收 Java 函数式接口(带单抽象方法的接口,也叫作 SAM 接口)作为形参的方法。
- 带接收者的 lambda 是一种特殊的 lambda,可以在这种 lambda 中直接访问一个特殊接收者对象的方法。
- with 标准库函数允许你调用同一个对象的多个方法,而不需要反复写出这个对象的引用 。apply 函数让你使用构建者风格的 API 创建和初始化任何对象。
网友评论