Kotlin重学系列——高阶函数

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

前言

这是kotlin重学系列的第一篇,关于高阶函数,耐心读完本文你将学会:

  • 理解kotlin高阶函数的本质
  • 搞懂kotlin的lambda表达式
  • 了解kotlin的lambda和java lambda的区别

为什么把高阶函数作为kotlin重学系列的的第一篇?没错,就是因为它重要!!!如果你没掌握高阶函数,kotlin很多语法看起来就会很难受,甚至完全看不懂,例如kotlin扩展,协程,DSL等等,这些几乎都跟高阶函数有密切联系,可以说不会高阶函数,和写java没区别

函数类型

java中有函数类型么?没有,java的数据类型只包括int,long等基本数据类型以及String,数组等引用类型

那kotlin为什么又引入一个函数类型?有什么作用?

假设现在有这样一个逻辑:我们自己定义了一个函数,但是这个函数里面有一部分逻辑想要调用者来定义,也就是调用者传递一段代码逻辑进来,这个用java来实现有办法么?

有,利用接口,例如监听点击事件SetOnClickListener方法,它接收的就是一个接口,我们匿名实现OnClickListener接口,就实现了把一段代码逻辑传递了过去

因为java不支持函数类型,只能把这个函数用接口再包一层,所以这属于是一种曲线救国的方式,平时有IDE的自动补全功能可能也都觉得无所谓,但我们为了实现这样很常见的逻辑,每次都要定义一个接口,然后再去实现它,看着那么多冗余的代码多少有点儿蛋疼吧

所以kotlin中引入了函数类型就是为了解决这类问题,让函数也可以直接作为另外一个函数的参数或者返回值

函数类型的表示

因为不同函数之间,主要就是入参和返回值的不同,所以函数类型的表示公式如下:

(参数)->返回值

箭头左边的括号里表示的是入参的类型信息(有多个参数用“,”分割),箭头右边就是返回值类型信息,看栗子

// 表示的函数有两个Int类型的参数,返回值也是Int
(IntInt)->Int

// 函数没有入参,返回值是空的
()->Unit
复制代码

高阶函数

定义:如果一个函数的参数或者返回值是函数类型,那么这个函数就称之为高阶函数

例如

// 定义一个函数funA,接收一个函数类型的参数
fun funA(funParam: (Int) -> Int) {
    funParam(1)
}

// 定义一个函数funB,该函数入参为一个Int,返回值为一个Int
fun funB(param: Int): Int {
    return param+1
}

// 调用
funA(::funB)

复制代码

如上代码,函数funA就是一个高阶函数,因为他的入参包含了一个函数类型

又定义了一个函数funB,入参和返回值正好满足funA接收的函数类型,所以就可以把funB这个函数传递给funA

注意我们在调用的时候,不是直接把funB这个名字标识符传给funA,而是在前面加了一个::,那这个“::”是干什么的呢?

这个双冒号的写法叫做函数引用,也就是说::funB这个是指向函数funB的,为什么又需要整这些花里胡哨的,因为函数他毕竟本质还是一个函数,函数之所以可以作为参数传递给另外一个函数,他的本质是kotlin可以把函数包装为一个对象,也就是说::funB是一个函数类型的对象

不过实际开发中我目前很少用这种方式,大多数都是用匿名函数和lambda,这个才是重点,下面马上会讲到

匿名函数

没有名字的函数就叫匿名函数,还是上面那个demo,我们完全可以不单独定义一个函数funB,而是直接用匿名函数


funA(fun(param:Int):Int{
    return param+1
})

// 或者可以直接把匿名函数赋值给一个变量
val d = fun(param: Int): Int {
    return param+1
}
funA(d)
复制代码

我们看到这里匿名函数可以直接传递给函数,也可以直接赋值给变量,所以它跟上面的::funB就是等价的,也就是说匿名函数实质上是一个函数类型的对象

lambda

上面使用匿名函数的方式看着还是有点别扭,总感觉很臃肿,而kotlin的宗旨就是简洁,所以就有了lambda,像下面这样

funA({param:Int ->
    param+1
})
复制代码

lambda在匿名函数的基础上又省略了一些代码,包括省略了fun关键字,把参数也直接放在了大括号里面,这样看起来简洁了不少,不过lambda的简洁还不远不如此,下面会一步步简化

先看看lambda的标准格式

{参数->函数体}

lambda的内容都在一个大括号里面,用箭头分割成两部分,箭头左边是参数,箭头右边是函数体,如果没有参数,箭头以及左边的就直接省略不写

// 没有参数直接省略箭头和左边的内容
val lambda1 = { println("无参数的lambda") }


val lambda2 = { param: Int ->
    println("带参数的lambda$param")
}

// lambda的函数体的最后一行的结果表示的就是返回值
val lambda3 = { param: Int ->
    println("带参数的lambda$param")
    param
}
复制代码

上面的栗子展示了有参数和没有参数的lambda情况,另外值得注意的是lambda的最后一行的执行结果就是这个函数的返回值,例如上述lambda2的最后一行为println("带参数的lambda$param"),所以他的返回值类型为Unit,lambda3的最后一行是param,所以他的返回值就是这个param,也就是Int类型的

下面继续看看lambda表达式还能怎么简写

1.如果lambda是函数的最后一个参数,那么就可以把lambda写在括号外面

funA(){ param: Int ->
    param + 1
}
复制代码

2.如果函数只有lambda一个参数,括号里面就是空的,写了有何用,省略

funA{ param: Int ->
    param + 1
}
复制代码

3.kotlin支持类型推导,所以lambda中的参数类型也能省略

funA(){ param ->
    param + 1
}
复制代码

4.如果lambda只有一个参数,也可以省略

funA {
    it + 1
}
复制代码

如果lambda只有一个参数的时候,会默认提供一个名为it的参数来表示这个入参,在lambda内可以直接使用,不用声明

为什么越来越任性,啥都可以省略,那是因为kotlin坚持只要能推断出来的地方,能不写的代码就不写,例如这个地方的参数为什么可以省略,就是因为他能推导出来,从哪儿推导呢?肯定是函数定义的地方,函数funA在定义的时候就声明了这个函数的类型,包括参数类型,返回类型

那如果lambda有多个参数了?

fun funAA(funParam:(Int,Int)->Int){
    funParam(1,1)
}

// 必须写上参数,如果参数没有被使用,可以使用_代替
funAA { _, i2 -> i2+1 }
复制代码

多个参数就需要指明参数的名字了,如果参数没有使用,就可以使用_代替

kotlin的lambda和java的lambda的对比

java的lambda是在java8引入的,是针对单抽象SAM(Single Abstract Method)接口的简化使用,例如Runnable

// 原始写法
new Thread(new Runnable(){

    @Override
    public void run() {
        
    }
});

// 引入lambda之后
new Thread(() -> {
    int i = 1;
});

复制代码

而kotlin的lambda是一个函数对象,划重点它是一个对象,是一个函数类型的对象,所以它可以作为参数直接赋值给别的变量,而java中的lambda是做不到的,他只能用在单抽象接口的地方,只是一种写法上的变化,本质并没有改变,还是匿名内部类

在kotlin中,针对这种SAM接口,也同样做了兼容转换,直接把这种使用SAM接口的函数变成了一个高阶函数,所以也可以直接应用kotlin的lambda

Thread { val i = 1 }
复制代码

以上就是kotlin高阶函数的全部内容,其实没有什么难度,只是刚开始学可能有点不太习惯,但还是要说kotlin的高阶函数非常重要,尤其是lambda,只要平时多写,都很简单

参考大佬文章:

Kotlin 高阶函数

Kotlin 的 Lambda 表达式

猜你喜欢

转载自juejin.im/post/7085289419850121252