8.kotlin函数式编程以及lambda表达式
lambda表达式,简单来说就是一种匿名函数的表达式,通过上下文类型推断,可以省略很多代码,Lambda表达式是一种语法糖,它能帮助我们写出更简洁,更容易理解的程序。所以在开始函数式编程之前,我们要先掌握Lambda表达式到底是什么,它的原理是什么。
1.高阶函数(higher-Order Function)
在理解Lambda表达式之前,首先我们需要知道kotlin的高阶函数,那什么是高阶函数呢
可以使用函数作为参数,或者返回值是一个函数的函数就叫做高阶函数
在java中,如果我们想要在一个函数中调用另外一个函数,我们可以这样做
int a(int x){
return b(x)+1;
}
int b(int x){
return x+1;
}
但是如果我想动态设置的并不是方法的参数,而是方法的名称,那该如何处理
比如同样有一个函数a(),接收一个函数参数,通过传入的函数来决定a()里面调用的是哪一个参数,例如
int a(??? method){
return method()+1;
}
在java中,方法参数只能是对象或者基本数据类型,而函数是不可以被当做参数传递的,所以上面的做法是不可行的。
如果非要实现这种方式的话,Java中有一个历史悠久的方式,那就是接口,通过一个接口,把需要传递的函数进行包装,当做一个接口对象传递到方法里面去,例如
//使用一个接口包装method方法
public interface Wrapper{
int method();
}
//通过传递接口类型的参数,使得a()可以调用method方法
int a(Wrapper wrapper){
return wrapper.method()+1
}
如果使用具体的例子,就可以参考view中的点击事件的回调
//这是伪代码
public class View{
...
OnClickListener mOnClickListener;
...
public void onTouchEvent(MotionEvent e){
...
mOnClickListener.onClick(this)
...
}
public interface OnClickListener{
void onClick(View v);
}
}
...
view.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
...
}
})
可以看到上面的OnClickListener接口只是一个壳,真正有效的是onClick()函数,因为java不允许函数参数时一个函数,所以才使用这种这种的方法,把函数包装,然后进行传递。
当然,在kotlin中,函数的参数可以是一个函数类型的,当一个函数含有函数类型的参数的时候,当你调用的时候,就必须传入一个函数类型的对象给它
但是又有个问题,在kotlin中,是没有函数类型这种类型的,只能说是一类类型,因为函数可以是各种各样的,不同的参数,不同的返回值类型搭配形成的,无法使用一个固定的类型来定义
比如说
() -> Unit //无参数无返回类型
Int->String //参数时int,返回值是String类型的
这都是一些函数,但是这些函数由于参数或者返回类型都不一样,所以我们认为它们是不同的函数
同样,在函数的参数中,就无法直接使用Fun来表示一个函数类型,而是需要指定具体是哪种的函数类型或者说这个函数类型的参数以及返回值类型。
对于具体的函数类型,我们一般这样表示
//括号里面代表的是函数的参数,可以为多个
//箭头(->)后面表示的是函数的返回类型
(Int,String,...)->[String] [Int][Unit]...
//参数类型为 '(Int)->String' 返回值为String的函数
// (Int)->String) 为函数类型
fun a(funParam:(Int)->String):String{
return funParam(1)
}
//普通的函数 用类型表示,可以表示为 (Int)->String
fun b(param:Int):String{
return param.toString()
}
//参数为类型为Int, 返回类型为 (Int)->String 的函数
//函数类型 (Int)->String 可以作为返回值,
fun c(param: Int):(Int)->String{
//上面的函数b() 可以用 (Int)->String表示 ,与本函数的返回值类型相同
//使用 ::b 来表示b函数的对象
return ::b
}
像上面的参数时函数或者返回类型是函数的函数我们就把他称之为高阶函数
所谓的高阶并没有特殊的函数,并不是文字表达那样,是什么更高一级的东西,它的概念跟数学中的高阶函数是一样的。
如果一个函数使用函数来作为它的参数或者结果,它就被称作为高阶函数,比如说求导,就是一种典型的高阶函数
在kotlin中,函数是可以被赋值的,比如
fun b(param:Int):String{
return param.toString()
}
val d=::b //双冒号表示函数引用
fun test(){
a(::b)//函数类型的对象可以作为参数
val d=::b //函数类型的对象可以直接赋值
(::b)(1) //函数类型的对象可以直接当方法调用
d(1) //赋值也是一个函数类型的对象,同样可以直接调用
(::b).invoke(1) //函数类型的对象,都有invoke()
val e= a(fun(param:Int):String{
return param.toString()
})
}
在上面中,::b跟函数b()是一个东西,但是无论是kotlin还是java,函数中接受的参数都是基本类型或者说是对象,所以在kotlin中,需要加上双冒号,才能变成一个对象,作为参数传递到函数中
kotlin中,函数名加上双冒号,其实是等价于创建了一个和函数具有相同功能的对象,当加上双冒号之后,以及不表示函数的本身了,而是表示一个跟函数具有相同功能的对象,这样才能在函数中进行传递。单值得注意的是kotlin的双冒号是创建了跟原函数具有相同功能的对象,并不是指向原函数的一个引用,是复制,而不是直接指向,这个说法需要理解好
lambda
了解了高阶函数之后,其实对于lambda,可以说是很容易理解的了,对于一个可以接受函数参数的函数中,比如说java中的回调,典型的就是监听器,我们可以不需要包装成一个对象类型,而是直接可以使用高阶函数的写法,忽略掉一些额外的东西,只关注函数的本身,来达到简化的效果,这就是lambda表达式。
对于上面所获的view的点击事件,lambda可以写成这样,例如
//java写法
view.setOnClickListener(new OnClickListener(){
public void onClick(View v) { ... }
});
//kotlin接口回调写法,无lambda写法
view?.setOnClickListener(object: OnClickListener(){
public fun onClick(v:View) { ... }
})
//一般写法
view?.setOnClickListener(fun(v:View):Unit{
...
})
//简化写法
view?.setOnClickListener({v:View->
})
view?.setOnClickListener(){ v:View->
//如果lambda是函数的最后一个参数,可以卸载括号后面
}
view?.setOnClickListener(){
//如果lambda是函数的唯一参数,可以把这个参数省略
//如果需要使用到这个参数,可以使用 it 来表示
}
对于lambda的写法,看起来确实是简洁了很多,可以少写很多代码,那么问题来了,lambda省略了这么多代码,那它又是如何确定自己的参数类型和返回值类型的呢
其实在函数声明的地方就已经确定了函数的参数类型以及返回类型了,如下
var onClickListener:((View)->Unit)?=null
fun setOnClickListener(listener:(View)->Unit){
this.onClickListener=listener
}
在这里就可以明确参数信息,所以在调用的时候,就可以把一些已经声明的信息省略,所以lambda就可以不写,简单点来说,就是通过上下文来判断类型是什么。
在kotlin中, 匿名函数并不是一个函数,而是一个对象,一个函数类型的对象,它和双冒号函数名(::b)是一个东西,和函数不是。
因为只有对象才可以在方法中传递。同理lambda其实也是一个函数类型的对象。
总结
1.参数可以是函数或者返回类型是函数的函数,我们称之为高阶函数
2.在kotlin中,函数是不能作为参数进行传递的,传递的都是函数类型的对象,"(Int)->Unit" 这就表示一个参数时Int,返回值是空的函数类型。
3.对于一个使用函数作为参数的函数,在时候的时候,可以把一些无关紧要的代码省略掉,达到简化的效果,这就是lambda表达式
4.kotlin中的匿名函数并不是函数,而是一个函数类型的对象,跟双冒号函数名是一个东西,跟lambda也是一个东西。