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也是一个东西。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352