lambda函数式编程

lambda表达式结构是:{参数名: 参数类型, 参数名: 参数类型 … -> 函数体}(参数1, 参数2, …)

如我们要找出字符串数组的最长的那个单词,可以写成:

1
2
3
4
5
6
7
8
9
10
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var maxLengthFruit = ""
for (fruit in list) {
if(fruit.length > maxLengthFruit.length) {
maxLengthFruit = fruit
}
}
println(maxLengthFruit)
}

如果我们想写成lambda表达式的话,可以用$\texttt{maxBy}$这个函数,该函数工作原理是接收一个lambda表达式,根据传入的条件来找到最大值。那么上述的代码就可以写成:

1
2
3
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda = {fruit: String -> fruit.length}
val maxLengthFruit = list.maxBy(lambda)

然后对他进行简化,首先$\texttt{maxBy}$里面可以直接传lambda参数:

1
val maxLengthFruit = list.maxBy({fruit: String -> fruit.length})

然后$\texttt{Kotlin}$还规定,当lambda参数是函数最后一个参数时,可以将{}放在()后面:

1
val maxLengthFruit = list.maxBy(){fruit: String -> fruit.length}

如果lambda参数是函数唯一参数的话,还可以直接省略():

1
val maxLengthFruit = list.maxBy{fruit: String -> fruit.length}

然后加上$\texttt{Kotlin}$的优秀推导类型的机制,可以直接省略类型,然后当参数只有一个时,可以直接用it代替:

1
val maxLengthFruit = list.maxBy{it.length}

这样一步步推导就可以推导出最简形式了。

map函数

$\texttt{map}$函数非常常用,可以把集合的每个元素都映射成另一个值,规则则是由接受的lambda表达式决定。比如我们要将水果名变成大写:

1
2
3
4
5
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map{it.toUpperCase()}
for(fruit in newList) {
println(fruit)
}

运行结果就是:

1
2
3
4
5
6
APPLE
BANANA
ORANGE
PEAR
GRAPE
WATERMELON

filter函数

$\texttt{filter}$函数可以过滤集合中的数据,也同样接受lambda表达式。,可以搭配$\texttt{map}$函数使用:

1
2
3
4
5
6
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)
}

运行结果就是PEAR了。

Java函数式API使用

当我们用Kotlin调用Java方法时,如果这个方法只接受一个Java单抽象方法接口参数就可以换成函数式API。单抽象方法接口指这个接口中只有一个待实现的方法。比如线程中的$\texttt{Runnable}$接口,就只有一个run()方法:

1
2
3
public interface Runnable {
void run();
}

我们在$\texttt{Thread}$类中接受一个$\texttt{Runnable}$参数:

1
2
3
4
5
6
new Thread(new Runnable() { //使用匿名内部类
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();

在Kotlin中,没有new这个关键字,改成了object关键字:

1
2
3
4
5
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()

然后这个Thread类的写法符合函数API的形式,故可以简化:

1
2
3
Thread(Runnable {
println("Thread is running")
}).start()

然后如果Java方法参数列表只有一个Java单抽象方法接口参数,可以直接对接口名省略:

1
2
3
Thread({
println("Thread is running")
}).start()

然后根据上面的简化还可以将()省略:

1
2
3
Thread{
println("Thread is running")
}.start()

同样还有最常用的点击事件接口OnClickListener,也符合函数式API的形式:

1
2
3
4
5
6
botton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

}
});

Kotlin中则可以简化成:

1
2
3
button.setOnClickListener {

}

就可以精简很多代码了。

let,with,apply

这三个函数均提供了函数API编程接口,即能接受lambda表达式

let

1
2
3
obj.let {obj2 ->
//业务逻辑
}

在上述代码中会自动将obj参数传给obj2,为了防止重名才取不同的名字

并且当只有一个参数时,可以用it代替。如现在有一个study类,类中有readBooks(),doHomework()两个方法,可以变成:

1
2
3
4
5
6
fun doStudy(study: Study) {
study.let {
it.readBooks()
it.doHomework()
}
}

通常在判空时使用

with

with函数接收两个参数,第一个参数是任意类型的对象,第二个参数是lambda表达式,在lambda表达式中会提供第一个参数的上下文,并用最后一行作为返回值。如现在一个水果列表,要依次吃水果:

1
2
3
4
5
6
7
8
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val builder = StringBuilder()
builder.append("Start eating fruits.\n")
for(fruit in list) {
builder.append(fruit).append("\n")
}
val result = builder.toString()
println(result)

with方法就可以改成:

1
2
3
4
5
6
7
8
9
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val result = with(StringBuilder()) {
append("Start eating fruits.\n")
for(fruit in list) {
append(fruit).append("\n")
}
toString()
}
println(result)

另外,还有个run函数和这个类似,只不过run函数是在某个对象的基础上使用,同样接受lambda表达式,其他则是一样的,最后一行返回值:

1
2
3
4
val result = obj.run {
//上下文
"value"
}

apply

apply函数也很类似,只不过他不会返回一个值,而是自动返回调用对象本身:

1
2
3
4
val resullt = obj.apply {
//上下文
}
// result == obj

比如$\texttt{SharedPreference}$中就可以使用apply简洁的添加数据。