Lambda表达式在C++/Java中都支持,作为一种辅助语句。但是在Kotlin中,Lambda表达式的功能极其强大。
简化是Kotlin语言的思想之一,而在Lambda表达式的学习之中,该思想将会贯穿其中
集合的函数式API是用来入门Lambda编程的绝佳示例,不过在此之前,我们得先学习创建集合的方式才行
在Kotlin中,提供了一个listof() 的内置函数来帮助我们创建一个集合,用法如下:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
这里,我们创建了一个水果的集合。值得注意的是,我们使用的listof() 函数创建的是一个不可变的集合,该集合只能用于读取,我们无法对集合进行添加、修改或删除操作。如果我们需要创建一个可变的集合,我们可以使用mutableListof() 函数。使用方法与listof相同。
首先我们来思考一个需求,如何在一个水果集合里面找到单词最长的那个水果?在Kotlin中,我们可以写出如下代码:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)
这里使用了函数式APImaxBy()。 maxBy是一个函数,只不过它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数的工作原理是根据我们传入的条件来遍历集合,从而找到该条件下的最大值,比如说想要找到单词最长的水果,那么条件自然就应该是单词的长度了。因此,我们可以推断出 it.lenth就是一个Lambda表达式。更确切地说,这是简化后的Lambda表达式。
首先来看一下Lambda的定义,如果用最直白的语言来阐述的话,Lambda就是一小段可以作为参数传递的代码。从定义上看,这个功能就很厉害了,因为正常情况下,我们向某个函数传
参时只能传入变量,而借助Lambda却允许传入一小段代码,其语法结构如下:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
值得注意的是,Lambda表达式的最后一行代码会自动作为Lambda表达式的返回值。
这一部分,我们来介绍Lambda表达式简化的一些规则。
对于找到单词最长的水果,我们一开始可以写出这样的代码:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)
首先,我们不需要专门定义一个lambda变量,而是可以直接将lambda表达式传入maxBy函数当中,因此第一步简化如下所示:
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
根据简化规则1,我们继续将该行代码简化:
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
根据简化规则2,我们继续将该行代码简化:
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
根据简化规则3,我们继续将该行代码简化:
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
根据简化规则3,我们继续将该行代码简化:
val maxLengthFruit = list.maxBy { it.length }
于是乎,我们就得到了与开头我们提供的代码一模一样的代码。
如下所示的代码是将所有的水果名都变成大写模式:
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map { it.toUpperCase() }
for (fruit in newList) {
println(fruit)
}
}
如下所示的代码是保留5个字母以内的水果,并将其单词全部大写:
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.filter { it.length <= 5 }
.map { it.toUpperCase() }
for (fruit in newList) {
println(fruit)
}
}
any函数和all函数的用法如下:
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val anyResult = list.any { it.length <= 5 }
val allResult = list.all { it.length <= 5 }
println("anyResult is " + anyResult + ", allResult is " + allResult)
}
在Kotlin中调用Java方法时也可以使用函数式API,只不过这是有一定条件限制的。具体来讲,如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数
式API。
Java原生API中有一个最为常见的单抽象方法接口——Runnable接口。这个接口中只有一个待实现的run()方法,定义如下:
public interface Runnable {
void run();
}
根据前面的讲解,对于任何一个Java方法,只要它接收Runnable参数,就可以使用函数式API。那么什么Java方法接收了Runnable参数呢?这就有很多了,不过Runnable接口主要还
是结合线程来一起使用的,因此这里我们就通过Java的线程类Thread来学习一下。
Thread类的构造方法中接收了一个Runnable参数,我们可以使用如下Java代码创建并执行一个子线程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();
注意,这里使用了匿名类的写法,我们创建了一个Runnable接口的匿名类实例,并将它传给了Thread类的构造方法,最后调用Thread类的start()方法执行这个线程。
而如果直接将这段代码翻译成Kotlin版本,写法将如下所示:
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()
Kotlin中匿名类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用了object关键字。这种写法虽然算不上复杂,但是
相比于Java的匿名类写法,并没有什么简化之处。但是别忘了,目前Thread类的构造方法是符合Java函数式API的使用条件的,下面我们就看看如何对代码进行精简,如下所示:
Thread(Runnable {
println("Thread is running")
}).start()
这段代码明显简化了很多,既可以实现同样的功能,又不会造成任何歧义。因为Runnable类中只有一个待实现方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容。
另外,如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略,这样代码就变得更加精简了:
Thread({
println("Thread is running")
}).start()
不过到这里还没有结束,和之前Kotlin中函数式API的用法类似,当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略,最终简化结果如下:
Thread {
println("Thread is running")
}.start()
在Kotlin中调用Java函数式API最经典的例子当属点击事件的接口了。
Android中有一个极为常用的点击事件接口OnClickListener,其定义如下:
public interface OnClickListener {
void onClick(View v);
}
可以看到,这又是一个单抽象方法接口。假设现在我们拥有一个按钮button的实例,然后使用Java代码去注册这个按钮的点击事件,需要这么写:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
而用Kotlin代码实现同样的功能,就可以使用函数式API的写法来对代码进行简化,结果如下:
button.setOnClickListener {
}
而最后这段给按钮注册点击事件的代码,将会在Android开发时经常用到。