Lambda表达式
不少人接触lambda表达式是从Java8开始的
//匿名内部类写法
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
//lambda写法
view.setOnClickListener(v -> {
});
很多人在java里用lambda表达式都是借助IDE的代码补全功能,如果离开IDE的代码补全是不会想去用它的,因为lambda不能在代码结构上改变什么,它仅仅是简化了匿名内部类的写法,让我们不至于看到代码里的过多缩进。
而Kotlin的lambda则不同于java,其是有实实在在的意义的。lambda表达式在kotlin里可以看做是一个匿名函数,其描述了函数的输入输出类型。在kotlin里lambda一般是当作方法的一个参数使用,而在java里函数是不能作为参数的,这是两者的区别。如果一个函数的参数或返回值是另一个函数,那我们称这种函数为高阶函数。
高阶函数 Higher-order function
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
接受一个或多个函数作为输入
输出一个函数
以上是高阶函数的数学定义,在java中是没有高阶函数这种定义的,但是java要实现类似功能也不是不可以。比如一个函数要动态接受另一个函数在java中可以通过接口实现,比如:
public interface OnClickListener {
void onClick(View v);
}
view.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
}
});
这个代码大家应该都不陌生,因为java的函数参数只能是类或接口,所以只能把要执行的方法包在一个接口里然后在执行接口的onClick()方法。但其实我们真正要执行的仅仅是onClick的方法本身,只是因为java不支持方法作为参数,所以代码才不得不以这样的形式设计。
高阶函数代码表现
如果函数支持函数作为参数,代码应该怎么写呢?这样:
Fun fun1
view.setOnClickListener(funParam:Fun,paramB :Int){
fun1 = funParm
}
...
fun(parmA,paramB...)
...
大概就是这样的一个形式,以上代码属于任何语言,只是表达了如果函数作为参数代码大概是个怎么样的形式。就是把方法作为一个方法对象,这个对象跟其他对象一样被调用,这样是不是比包装接口方便了?但是这个写法比较简陋,实际语言的语法肯定不是这样。首先方法肯定是无法定义为一个类型的,因为可以定义无数种。不过要确定某一个方法类型很简单,只要确定方法的参数和返回值类型完全一样,那么我们就认定这样的方法是同一个类型。在kotlin里是这样定义一个方法类型的。
(param1,param2,...)-> returnType
比如:(String,Int) -> Int
//这定义了一个参数为String和Int类型,返回为Int类型的一个函数,所以符合此规则的都认定是同一个函数类型
那么在kotlin里的表现是什么样呢?
class View {
...
//方法定义了一个参数为View返回类型为空的一个函数类型参数
fun setOnClickListener(clickFun: (View) -> Unit) {
clickFun(this)
...
}
...
}
fun test{
val clickFun = ::clickFunction //声明一个函数类型对象
view.setOnClickListener(clickFun)
}
//这个方法的参数是View返回类型为空,所以这个方法可以用来声明setOnClickListener的参数类型
fun clickFunction(view: View) {
...
}
这种写法跟我们调用普通方法区别其实也不是很大,主要有两种区别:
参数类型:普通参数类型一般是一个类对象,一般由一个或多个单词组成,比较简单如String,StringFormatter,函数类型参数则由两部分组成,(入参类型)->返回类型,如(View) -> Unit,稍微复杂
对象声明:java对象声明是new 类名(构造参数) kotlin省掉了new,直接是类名(构造参数);函数类型的声明则是用“::”,双冒号在kotlin的官方称为“Function Refrence,用双冒号声明的对象则为函数类型对象。
调用:对象调用方法一般都是Object.method(),而函数类型对象本身就是方法了,也就不能用这种方式了。其执行方式有两种 :Function(param) 或 Function.invok(param)
注意:kotlin的函数也是不能作为参数传递的,能作为参数传递的一定是对象,所以我们这里的参数是"::clickFunction"这个函数类型对象,而不是函数本身。
匿名函数
java的匿名内部类大家肯定都用过,用起来比较方便,在读代码时可读性比较强。而kotlin的函数也有对应的匿名函数写法,比如:
val clickFun = fun clickFunction(view: View) {}
这样写对么?由于这是匿名函数,所以原先的函数名就没有意义了,所以上面的写法是不对的,需要去掉原先函数名。
val clickFun = fun (view: View) {}
所以之前的调用方式也可以改成下面这样
fun test{
//val clickFun = ::clickFunction //声明一个函数类型对象
view.setOnClickListener(fun (view: View) {
...
})
}
匿名内部类是一个函数类型对象,它不是函数,其在类的方法体里是不能直接调用的,一定是通过对象的引用来调用,所以匿名函数才能直接作为参数传递。
不是说Lambda么,怎么说了半天函数?函数跟Lamda有关系么?别急,下面就来解答。
Lambda是什么
Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数
以上是百科的定义,所以在kotlin里,Lambda表达式可以看作是匿名函数的表现形式,且大多数时候写法更简单。比如上面的匿名函数写法可以这样写:
view.setOnClickListener({ v:View -> })
//如果lambda是函数最后一个参数,可以把lambda写到括号外面
view.setOnClickListener(){ v:View -> }
//如果lambda是函数唯一的参数,可以把括号去掉
view.setOnClickListener{ v:View -> }
//如果lambda是单参数,那么这个参数也可以去掉
view.setOnClickListener{ it.visbility = View.GONE}
参数如果省略不写,那要用的时候怎么办呢?kotlin对只有单参数的lambda统一使用"it"作为变量名。
这时候不少人就要问了,lambda省略这么多东西是怎么做到正确执行的呢。其实很好理解,因为在函数定义的地方已经声明好相应参数类型,所以kotlin从代码上下文推倒出了相关逻辑。
Lambda语法
语法基本有以下几种:
//声明lambda变量
val lambda : (param:Type,...) ->Type
val lambda = {} //无参数
val lambda = {a:Int,b:Int ->} //有参数初始化,语法同kotlin所有对象初始化声明
Lambda表达式在多参数方法中的应用
我们在定义高阶函数时,尽量吧函数类型参数放在最后一个,因为如果函数类型参数是最后一个参数时,可以把lambda写到括号外面
fun fun1(param:Int,clickFun: (String,Int) -> Unit) {
clickFun("this",0)
}
//因为lambda是最后一个参数,所以lambda可以放在方法括号之外
fun1(0){p1,p2 ->
println("$p1 $p2")
}
如果lambda不只是最后一个参数是这样的
fun fun1(clickFun: (String,Int) -> Unit,param:Int) {
clickFun("this",0)
}
fun1({p1,p2->
println("$p1 $p2")
},0)
lambda不是最后一个参数,那么它要写在函数括号里面
总结
其实Lambda没有想象中的那么复杂,我们只要了解其本质就能很好的在我们的代码逻辑中运用。在kotlin中如果能熟悉运用Lambda表达式,就能替代java中接口回调式的代码结构。我们的代码会变得很清爽,可读性会增强不少。