来源 kotlin 实战,业余时间整理的笔记。
lambda
lambda
本质上就是可以传递给其他函数的一小段代码,也就是可以作为函数参数的一段代码
Java 8
之前可以通过 匿名函数 实现,简单例子如下:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick (View view) {
// 点击后执行的动作
}
}
)
kotlin
中使用:
button.setOnClickListener{
// 点击后执行的动作
}
lambda
表达式的经典用途:和集合一起工作
假设现在你有一个人的列表,需要找到列表中年龄最大的那个人。如果完全不 了解 lambda
,我们会先引入两个中间变 量, 一个用来保存最大的年龄,而另一个用来保存找到的此年龄的第一个人。然后迭代整个列表,不断更新这两个变量。
fun findTheOldest(people: List<Person>){
var maxAge = 0
var theOldest: Person? = null
for (person in people) {
if (person.age > maxAge){
maxAge = person.age
theOldest = person
}
}
println(theOldest)
}
>>> val people = listOf(Person("Alice", 29), Person("Bob", 31))
>>> findTheOldest(people)
Person(name=Bob, age=31)
使用 lambda
在集合中搜索:
>>> val people= listOf(Person {"Alice", 29) , Person ("Bob", 31))
>>> println (people. maxBy { it . age } )
Person(name=Bob, age=31)
maxBy
函数可以在任何集合上调用,且只需要一个实参 : 一个函数,指定比
较哪个值来找到最大元素。花括号中的代码{ it. age }
就是实现了这个逻辑的 lambda
。
它接收一个集合中的元素作为实参(使用it
引用它)并且返回用来比较的值。
这个例子中, 集合元素是 Person
对象, 用来比较的是存储在其 age
属性中的年龄。
Lambda 表达式的语法
Kotlin 的 lambda 表达式始终用花括号包围。注意实参并没有用括号括起来。 箭 头把实参列表和 lambda 的函数体隔开。
如果不用任何简明语法来重写这个例子, 你会得到下面的代码:
简明语法:
people. maxBy { it . age }
不使用简明语法:
people.maxBy ({ p: Person-> p.age }
Kotlin
有这样一种语法约定,如果 lambda
表达式是函数调用的最后一个实参,它可以放到括号的外边。
这个例子中, lambda
是唯一的实参,所以可以放到括号的后边:
people.maxBy () { p: Person 『> p.age }
当 lambda
是函数唯一的实参时,还可以去掉调用代码中的空括号对:
people.maxBy { p: Person -> p.age }
如果当前 上下文期望的是只有一个参数的 lambda
且这个参数的类型可以推断出来,使用it
代替命名参数
仅在实参名称没有显式地指定时这个默认的名称 it
才会生成。
如果你用变量存储 lambda
, 那么就没有可以推断出参数类型的上下文,所以你 必须显式地指定参数类型:
>>> val getAge = { p: Person -> p.age }
>>> people.maxBy(getAge)
集合的函数式API
基础: filter
和 map
filter
函数遍历集合并选出应用给定 lambda
后会返回 true
的那些元素:
>>> val list= listOf(l, 2, 3, 4) >> println(list.filter { it % 2 ==0}) [2, 4]
map
函数对集合中的每一个元素应用给定的函数并把结果收集到一个新集合。
可以把数字列表变换成它们平方的列表,比如:
>>> val list= listOf(l, 2, 3, 4) >> println(list.map { it * it }l [1, 4, 9, 16]
如果你想打印的只是一个姓名列表,而不是人的完整信息列表,可以用 map
来 变换列表:
>>> val people = listOf(Person ("Alice", 29), Person ("Bob", 31))
>>> println(people.map { it.name })
[Alice, Bob]
可以轻松地把多次这样的调用链接起来。
例如,打印出年龄超过 30 岁的人的 名字 :
>> people.filter { it .age > 30 }.map(Person: :name)
[Bob]
如果说需要这个分组中所有年龄最大的人的名字,可以先找到分组中人 的最大年龄,
然后返回所有这个年龄的人。很容易就用 lambda 写出如下代码 :
people. filter {it.age== people.maxBy(Person::age) .age}
但是请注意,这段代码对每个人都会重复寻找最大年龄的过程,假设集合中有 100 个人,寻找最大年龄的过程就会执行 100 遍!
下面的解决方法做出了改进,只计算了一次最大年龄:
val maxAge = people.maxBy(Person::age).age
people.filter {it.age == maxAge }
“all”,“any”,“count"和"find”:对集合应用判断
为了演示这些函数,我们先定义一个判断式 canBeinClub27
,来检查一个人 是否还没有到 28 岁 :
val canBeinClub27 = { p: Person - > p.age <= 27 }
如果你对是否所有元素都满足判断式感兴趣,应该使用 all 函数
:
>>> val people= listOf(Person (”Alice ”, 27), Person(”Bob ”, 31)) >> printl口 ( people. all (canBeinClub27) ) false
如果你需要检查集合中是否至少存在一个匹配的元素,那就用 any 函数
:
>>> println(people any(canBeinClub27)) true
如果你想知道有多少个元素满足了判断式, 使用 count 函数
:
>>> val people= listOf(Person("Alice ”, 27) , Person (”Bob”, 31))
>>> println(people.count(canBeinClub27))
1
“count” vs “size” ,count 效率更高
原因是:一个中间集合会被创建并用来存储所有满足判断式的元素。 而另一方面 , count 方法只是跟踪匹配元素的数量,不关心元素本身,所以更 高效。
要找到一个满足判断式的元素,使用find 函数
:
>>> val people= listOf(Person (,。Alice ”, 27) , Person (”Bob”, 31))
>>> println(people.find(canBeinClub27))
Person(name=Alice, age=27)
groupBy :把列表转换成分组的 map
假设你需要把所有元素按照不同的特征划分成不同的分组。
例如, 你想把人 按年龄分组,相同年龄的人放在一组。把这个特征直接当作参数传递十分方便。
groupBy 函数
可以帮你做到这一点:
>>> val people= listOf(Person ("Alice", 31), Person("Bob", 29), Person ("Carol ", 31))
>>> println(people.groupBy { it.age })
这个例子的输出会是这样的:
{
29=[Person(name=Bob, age=29)],
3l=[Person(name=Alice, age=31), Person(name=Carol, age=31)]
}
flatMap 和 flatten :处理嵌套集合中的元素
flatMap 函数
做了两件事情:首先根据作为实参给定的函数对集合中的每个元 素做变换(或者说映射),
然后把多个列表合并(或者说平铺〉 成一个列表。 下面 这个字符串的例子很好地阐明了这个概念,如图 5.6 所示。
>>> val strings= listOf ("abc", "def")
>>> println(strings.flatMap { it.toList() })
[a, b, c, d, e, f]
如果你不需要做任何变换,只是需要平铺一个集合,可以使用 flatten 函数
: listOfLists.flatten() 。
带接收者的lambda: “with”与“apply”
“with”函数
很多语言都有这样的语句,可以用它对同一个对象执行多次操作,而不需要反 复把对象的名称写出来。 Kotlin 也不例外,但它提供的是一个叫 with 的库函数, 而不是某种特殊的语言结构。
构建字母表
fun alphabet(): String {
val result = StringBuilder()
for (letter in 'A'..'Z'){
result.append(letter)
result.append("\nNow I know the alphabet !")
return result.toString()
}
}
>> println(alphabet())
ABCDEFGH工JKLMNOPQRSTUVWXYZ
Now I know the alphabet!
使用 with
构建字母表
fun alphabet(): String {
val stringBuilder = StringBuilder()
return with(stringBuilder) {
for (letter in 'A'..'Z'){
// 显示使用 this
this.append(letter)
}
// 省掉 this
append ("\nNow I know the alphabet !")
// 从 lambda 返回值
this toString ()
}
}
使用 with 和一个表达式函数体来构建字母表
fun alphabet () = with (StringBuilder()){
for (letter in 'A'..'z'){
append (letter)
}
append ("\nNow I know the alphabet !")
toString ()
}
“apply”函数
apply 函数几乎和 with 函数一模一样, 唯一的区别是 apply 始终会返回作 为实参传递给它的对象(换句话说,接收者对象)。让我们再一次重构 alphabet 函数,这一次用的是 apply。
使用 apply 构建字母表
fun alphabet () = StringBuilder ().apply {
for (letter in 'A'..'Z') {
append (letter)
}
append("\nNow I know the alphabet !"")}
}. toString()
使用 apply
初始化一个 TextView
fun createViewWithCustomAttributes(context: Context) =
TextView(context).apply {
text = "Sample Text"
textSize = 20.0
setPadding(lO,0,0,0)
}
完!