Scope functions

let

let既不是操作符,也不是什么关键字,而是一个函数。这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中。

1
2
3
obj.let { obj ->
  // 编写具体的逻辑
}

如:

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

with

with函数接收两个参数:

第一个参数可以是任意类型的对象 第二个参数是一个Lambda表达式

with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式的最后一行代码作为返回值返回。

注意: with函数与python中的with不一样,与python中with作用类似的是Kotlin当中的use函数。

如下面的代码

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

使用with函数可以简化为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
val fruits = listOf("Apple", "Orange", "Pear", "Grape")
val result = with(StringBuffer()) {
	append("Start eating fruits.\n")
	for (fruit in fruits) {
		append("eat $fruit\n")
	}
	append("Ate all fruits")
	toString()
}
println(result)

run

run函数的用法和使用场景与with函数非常类似,只是语法上做了一些改动。

首先run函数是不能直接调用的,而是一定要调用某个对象的run函数才行;其次run函数只接受一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其它与with函数是一样的,包括也会使用Lambda表达式中的最后一句代码作为返回值返回。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
val fruits = listOf("Apple", "Orange", "Pear", "Grape")
val result = StringBuffer().run {
	append("Start eating fruits.\n")
	for (fruit in fruits) {
		append("eat $fruit\n")
	}
	append("Ate all fruits")
	toString()
}
println(result)

apply

apply函数与run函数非常类似,都要在某个对象上调用,并且只接受一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是自动返回调用对象本身。

1
2
3
4
5
6
7
8
9
val fruits = listOf("Apple", "Orange", "Pear", "Grape")
val result = StringBuffer().apply {
	append("Start eating fruits.\n")
	for (fruit in fruits) {
		append("eat $fruit\n")
	}
	append("Ate all fruits")
}.toString()
println(result)

also函数

交换两个变量的值

1
2
3
var a = 1
var b = 2
a = b.also { b = a }

几个函数的区别

集合的函数式API

  • maxBy函数: 将集合中的每一个元素按照传入的Lambda表达式进行转换,并返回转换后最大的值。

  • map函数: 将集合中的每一个元素都映射为另外的值,映射规则在Lambda表达式中指定,最终生成一个新的集合。

  • filter函数: 过滤集合当中的元素。

  • any函数: 用于判断集合中至少存在一个元素满足指定的条件

  • all函数: 用于用于判断集合所有的元素都满足指定的条件

1
2
3
4
5
6
val fruits = listOf("Apple", "Orange", "Pear", "Grape")
println(fruits.maxBy { it.length })
println(fruits.map { it.toUpperCase() })
println(fruits.filter { it.length < 5 }.map { it.toUpperCase() })
println(fruits.any { it.length < 5 })
println(fruits.all { it.length < 5 })

输出为

1
2
3
4
5
Orange
[APPLE, ORANGE, PEAR, GRAPE]
[PEAR]
true
false

Java函数式API

在Kotlin中调用一个Java方法时,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。

Java单抽象方法接口指的是接口中只有一个待实现方法,如果有多个待实现方法,则无法使用函数式API。比如: Java中的Runnable接口,这个接口有且只有一个待实现的run()方法,定义如下:

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

Thread类的构造函数接受一个Runnable参数,使用Java代码如下:

1
2
3
4
5
6
new Thread(new Runnable() {
	@override
	public void run(){
		System.out.println("Thread is running...");
	}
}).start();

上面使用了匿名类的写法,创建了一个Runnable接口的匿名类实例,并将它传给了Thread类的构造方法,最后调用Thread类的start()方法执行这个线程。

使用Kotlin,可以这样写

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

Kotlin中匿名类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名类的时候就不能再使用new了,而是改用了object关键字。

目前Thread类的构造方法是符合Java函数式API的使用条件的,上面的代码可以简化为

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

如果一个Java方法的参数列表中不存在一个以上的Java单抽象方法接口参数,可以将接口名进行省略。

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

和之前Kotlin中函数式API语法类似,当Lambda表达式是参数列表最后一个参数时,可以将Lambda表达式移到括号外面。同时,当Lambda表达式还是方法的唯一一个参数时,可以将方法的括号省略。最终简化为

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

类似的还有按钮点击事件的写法

1
2
3
4
5
6
button.setOnClickListener(new View.OnClickListener(){
	@override
	public void onClick(View v){

	}
});

使用Kotlin可以简化为

1
2
3
button.setOnClickListener {

}