前言
前面的几篇文章,我们学习了Kotlin类、接口、变量、函数、循环语句、条件语句等基础知识,这些基础知识已经足够让我们开始用Kotlin编程了,但总归还是带着Java面向对象编程的思想去编程,让我们在编程的时候,有种在用Kotlin写Java的感觉(如果你还没用Java8的前提下),可能会让不少人觉得Kotlin也就这样嘛,吹嘘的太厉害了,那么接下来的Kotlin进阶篇文章,将会带领大家深入的了解Kotlin,挖掘Kotlin函数式编程的知识点,给你带来不一样的感觉~
lambda表达式
什么是lambda ? lambda的本质其实就是可以传递给其他函数的一段代码,具体的语法结构如下图所示。
lambda表达式
lambda对于我们来说,用的最多的地方应该就用来替换匿名内部类,下面我们把view设置点击事件的代码用匿名内部类和lambda分别写一遍,看下区别在哪里。
//匿名内部类写法
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("tag", v.getId() + "点击事件");
}
});
//lambda写法
textView.setOnClickListener { view ->
Log.d("tag", "${view.id}点击事件")
}
可以看到,lambda相较于匿名内部类来说,写法上更简洁,可读性上也没有降低,而且就内存消耗而言,使用匿名内部类会显示的声明匿名对象,匿名对象每次调用时,都会生成一个新的变量,但lambda并不是,如果lambda表达式没有访问来自定义它的函数的变量,那么相应的匿名类实例是可以复用的,这个我们在下面的lambda在作用域中访问变量章节会说道。
lambda表达式在作为函数参数时,Kotlin有这么一个语法约定:
示例lambda表达式:
people.maxBy({ p: Person -> p.age })
- 当lambda表达式是函数调用的最后一个实参时,它可以放到括号的外面
people.maxBy(){ p: Person -> p.age }
- 当lambda表达式是函数唯一的实参时,空括号对可以省略
people.maxBy{ p: Person -> p.age }
- 当lambda的参数的类型可以根据上下文被推导出来时,可以不需要显示的指定参数的类型,当你不确定参数类型是否可以推导出来时,可以遵循这个简单的规则:先不声明类型,等编译器报错后再指定类型
people.maxBy{ p -> p.age }
- 当lambda只有一个参数,且参数类型可以根据上下文推导出来时,可以使用编译器生成的
it
参数名称来替代参数
people.maxBy{ it.age }
- 当函数有多个lambda表达式参数时,建议使用正常语法书写,不建议把lambda放在外部,会降低可读性
lambda表达式变量
lambda表达式可以存储在一个变量中,把这个变量当作普通函数对待。
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y } //完整语法,但通常我们会省略类型
val sum = { x: Int, y: Int -> x + y }
调用时,把变量当成函数,通过传入参数调用即可sum(1,2)
。此处涉及到函数类型,在这就不多聊了,后面我们说到高阶函数时,会详细的和大家聊聊。
lambda在作用域中访问变量
lambda在实际使用中,经常需要访问局部变量、全局变量、函数参数等,在Java8之前,如果我们要访问这些变量的话,都需要用final
修饰符修饰之后才可以访问。在Java8之后取消了这个访问限制,即使没用final
修饰也可以访问。但如果想修改这些变量的话,Java一般会声明一个单元素的数组,其中存储着要修改的变量,和Java不一样,Koltin则允许lambda内部访问非final变量和修改这些变量,我们称这些变量被lambda捕捉,看下代码。
// Java
private String x = "";
void test(String y) {
final String[] z = {""};
final String[] y1 = {y};
textView.setOnClickListener(v -> {
x = ""; //全局变量可以直接修改
z[0] = ""; //局部变量需存储在final修饰的数组中,修改数组元素来达到修改的目的
y1[0] = "a"; //形参需存储在final修饰的数组中,修改数组元素来达到修改的目的
});
}
//Kotlin
private fun test(y: String) {
var z = ""
var y2 = y
textView.setOnClickListener { view ->
run {
z = "a" //局部变量可直接修改
y = "b" //形参不可直接修改,是因为形参默认是val的即final的
y2 = "b"
}
}
}
对于为什么Kotlin可以直接在匿名实例中修改局部变量,其实很简单,我们看下编译后的文件就知道了
image
在了解了lambda如何在Kotlin中访问变量,我们接下来再看下,被访问的变量的生命周期。
在默认情况下,我们都知道局部变量的生命周期是和声明它的函数的生命周期一起的,但如果它被lambda捕捉了,那么使用这个变量的lambda相关代码则被存储起来并稍后执行(比如点击事件,则是在点击的时候材才执行),所以这个变量的声明周期也就不在跟着函数一起了,而是跟着捕捉它的lambda一起。
//这个例子中,局部变量title的声明周期,在函数执行完毕后,就被释放结束了
private fun test() {
val textView = TextView(this)
val title: String = ""
textView.setOnClickListener {
run{
}
}
}
//这个例子中,title的声明周期则在点击事件之后,才会被释放结束
private fun test() {
val textView = TextView(this)
val title: String = ""
textView.setOnClickListener {
run {
println("$title")
}
}
}
上面我们说过lambda相较于匿名内部类的优点,如果lambda没有捕捉变量的话,那么lambda创建的实例是可以复用的,但如果捕捉了变量,我们需要把这个变量和lambda一起存储起来使用,也就导致了我们每次使用lambda时,也需要创建一个新的实例了。
开篇我们说过lambda可以用来替换匿名内部类,之前的关于object
关键字的文章,我们也说过object
可以用来替换匿名内部类,那我们到底什么场景下用lambda表达式
,什么场景下用object
关键字呢?这里告诉大家,记住一个技巧,如果匿名内部类有多个待实现的方法时,选择使用object
关键字,如果只有一个待实现方法,则使用lambda
表达式,至于为什么一个待实现方法时建议用lambda而不是object?还记得前面我们刚说过的lambda相较于匿名内部类的内存消耗的优点吗?Bigo!。
好了,这篇文章我们主要了解了lambda的定义、语法规则、变量的捕捉等基础知识,希望能让你们对lambda有个基础的了解,下篇文章我们将学习lambda在集合中的实战应用,再见喽~
网友评论