1、Lambda的定义
Lambda就是一小段可以作为参数传递的代码。正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却能传入一小段代码
,那么多长的代码才能算一小段代码
,Kotlin不建议很长的代码,可读性太差。
接下来我们看下,Lambda表达式的语法结构:
{参数名1:参数类型,参数名2:参数类型 -> 函数体}
这是Lambda表达式最完整的语法结构。
- 1、首先最外层一对大括号。
- 2、如果有参数传入Lambda表达式,我们还需要声明参数列表。
- 3、参数列表的结尾使用
->
符号,表示参数列表的结束以及函数体的开始,函数体中代码长度不限,并且最后一行代码表示Lambda表达式的返回值
。
2、集合的函数式API
2.1、maxBy
集合的函数式API有很多个,这里并不打算讲解所有的函数式API的用法,而是重点学习函数式API的语法结构,也就是Lambda表达式的语法结构。
先看一个需求:如何从List集合中找到单词最长的那个水果。
val list = arrayListOf("Apple", "Banana", "Orange", "Pear")
list.add("Watermelon")
var maxLengthFruit = ""
for (fruit in list) {
if (fruit.length >= maxLengthFruit.length)
maxLengthFruit = fruit
}
println("单词最长的水果:"+maxLengthFruit)
代码很简单,如果使用集合函数式API来实现的会更简单
val list = arrayListOf("Apple", "Banana", "Orange", "Pear")
list.add("Watermelon")
var maxLengthFruit = list.maxBy { it.length }
println("单词最长的水果:" + maxLengthFruit)
list.maxBy { it.length }
直接看这个还是很难理解的,maxBy
就是一个普通的函数,只不过它接收的是一个Lambda类型的参数
,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy
函数工作原理是根据我们传入的条件来遍历集合,从而找到该条件下的最大值,比如我们想找到单词最长的水果,那么条件自然是单词的长度。
理解了maxBy
的工作原理, 我们就先用标准的Lambda的语法结构作为参数,传递给maxBy
函数。
val list = listOf("Apple", "Banana", "Orange", "Pear", "Watermelon")
val lambda = { fruit: String -> fruit.length }
//注意这里是小括号,括号中接收的Lambda参数
val maxLengthFruit = list.maxBy(lambda)
println("单词最长的水果:" + maxLengthFruit)
可以看到maxBy
中接收的是一个Lambda参数而已,并且这个Lambda参数是完全按照Lambda的标准结构定义的,这段代码虽然可以正常工作,但是需要简化的点很多,下面我们一步步简化。
- 1、首先我们不需要专门定义一个lambda变量,可以将lambda表达式传入
maxBy
函数中
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
- 2、Kotlin规定,当Lambda是函数的
最后一个参数
时,可以将Lambda表达式移到函数的括号外面
val maxLengthFruit = list.maxBy(){ fruit: String -> fruit.length }
- 3、如果Lambda是函数的唯一参数的话,可以省略函数的括号
val maxLengthFruit = list.maxBy{ fruit: String -> fruit.length }
- 4、由于Kotlin拥有出色的类型推导机制,所以Lambda的参数列表大多数情况下不需要声明参数类型
val maxLengthFruit = list.maxBy{ fruit -> fruit.length }
- 5、最后当Lambda表达式只有一个参数时,也没必要声明参数名称,可以用
it
代替
val maxLengthFruit = list.maxBy{ it.length }
经过一步步推导,终于简化完成了。接下来我们学习下几个集合中标常用的函数式API。
2.2、集合中的map函数
集合中的map
函数的作用:是将集合中的每个元素映射成另外的值,映射的规则是在Lambda表达式中指定的,最终生成一个集合。比如这里我们想让水果名变成大写的
val list = listOf("apple", "orange")
val newList = list.map { it.toUpperCase() }
for (fruit in newList){
println("fruit::"+fruit)
}
map函数的功能非常强大,它可以按照我们的要求对集合中的元素进行随意的映射转换,比如将水果名改成小写或者只取水果名的首字母,甚至是转换成单词长度的集合,只需在Lambda表达式中编写相应的逻辑即可。
2.3、filter函数
filter函数是用来过滤集合中的数据的,它可以单独使用也可以配合map
函数来使用。比如我们只想保留5个字母以内的水果,就可以借助filter
函数来完成,代码如下:
val list = listOf("apple", "orange", "watermelon", "banana")
val newList = list.filter { it.length <=5 }
.map { it.toUpperCase() }
for (fruit in newList) {
println(fruit)
}
另外值得一提的是,上述代码中我们先调用了filter
函数再调用了map
函数,如果改变调用的顺序,效果一样但是效率会低很多,其实很简单,因为这相当于对集合中所有元素都映射改变之后再过滤,肯定比只映射变换过滤后的效率低。
2.4、all和any函数
- 1、any:判断集合中至少一个元素满足指定条件
- 2、all:判断集合中所有元素是否都满足指定条件
val list = listOf("apple", "orange", "watermelon", "banana")
val anyResult = list.any { it.length <= 5 } //返回true
val allResult = list.all { it.length <= 5 } //返回false
println("anyResult is "+anyResult+" allResult is "+allResult)
3、Java函数式API的使用
在Kotlin中调用Java方法时也可以使用函数式API,只不过这是有一定的条件限制的,如果我们在Kotlin中调用Java方法,那么这个方法接收一个Java单抽象方法接口参数时就可以使用函数式API。Java的单抽象方法接口指的是,接口中只有一个待实现的方法。比如我们常用的接口Runnable
。
public interface Runnable {
void run();
}
根据前面的讲解,对于任何一个Java方法只要它接收Runnable
参数,就可以使用函数式API。比如我们在创建线程时就需要传入Runnable
参数。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程运行");
}
}).start();
注意这里使用了匿名类的写法,我们创建了一个Runnable
接口的匿名类然后作为参数传递给Thread
的构造函数。那么用Kotlin如何实现呢?
Thread(object :Runnable{
override fun run() {
println("子线程运行")
}
}).start()
Kotlin的匿名类的创建和Java有点区别,Kotlin中使用object
创建匿名类实例。但是别忘了,Thread的构造方法是符合Java函数式API的使用条件的,下面看下如何精简。
- 1、精简待实现方法
Thread(Runnable {
println("子线程运行")
}).start()
这段代码明显精简很多,但也不会造成歧义,因为Runnable
接口中只有一个待实现的方法,所以即使没有重写run()
方法,Kotlin也能自动明白Runnable
后面的Lambda表达式
就是run()
方法中实现的内容。
- 2、精简接口
如果Java方法的参数列表中只存在一个的Java单抽象方法接口参数,我们还可以对接口名进行省略。
Thread({
println("子线程运行")
}).start()
-
3、精简括号
和Kotlin函数式API一样,如果Lambda表达式是最后一个参数的话,可以将Lambda表达式一道小括号外面,并且如果只有这一个参数的话,可以省略小括号。
Thread{
println("子线程运行")
}.start()
再举个我们在Android中常用的点击事件的接口View.OnclikListener
,定义一个button
的点击事件如下:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
下面我们根据我们上面的分析,再次简化一下:
button.setOnClickListener(object: View.OnClickListener{
override fun onClick(v: View?) {
println("button is clicked")
}
})
//1、简化方法
button.setOnClickListener(View.OnClickListener{
println("button is clicked")
})
//2、简化接口
button.setOnClickListener({
println("button is clicked")
})
//3、简化括号
button.setOnClickListener{
println("button is clicked")
}
最后提醒一句:本小节学习的Java函数式API的调用只限定于从Kotlin中调用Java方法,并且单独抽象方法接口必须是Java语言定义的。
这么定义的原因是因为:Kotlin中有专门的高阶函数来实现更加强大的自定义函数式API功能,从而不需要像Java这样借助单抽象方法接口来实现。
网友评论