Android KotlinKotlin

Kotlin高阶函数详解

2022-07-27  本文已影响0人  光锥外

高阶函数是Kotlin函数式编程的基石,各种开源框架的关键元素,掌握了高阶函数对一些框架的源代码更容易理解,对学习Jetpack Compose也变得得心应手。

了解高阶函数

可以先看View.java中点击事件的代码,分析下:

这里如果借助高阶函数,可以一个接口都不定义。

//View.java
//成员变量
private OnClickListenermOnClickListener;
private OnContextClickListenermOnContextClickListener;
//监听手指点击事件
public void setOnClickListener(OnClickListenerl){
  mOnClickListener=l;
}
//为传递这个点击事件,专门定义了一个接口
public interface OnClickListener{
voidon Click(Viewv);
}
//监听鼠标点击事件
public void setOnContextClickListener(OnContextClickListenerl){
  getListenerInfo().mOnContextClickListener=l;
}
//为传递这个鼠标点击事件,专门定义了一个接口
public interface OnContextClickListener{
  booleanonContextClick(Viewv);
}

看上段代码的Kotlin等价代码:

//View.kt
//                (View)->Unit就是「函数类型」         
//                      ↑     ↑    
var mOnClickListener:((View)->Unit)? = null
var mOnContextClickListener:((View)->Unit)? = null
//高阶函数
fun setOnClickListener(l:(View)->Unit){
    mOnClickListener=l;
}
//高阶函数
fun setOnContextClickListener(l:(View)->Unit){
    mOnContextClickListener=l;
}

通过对比,我们可以发现Kotlin引入高阶函数,实际分为两个部分:

使用高阶函数好处

也带来了几个好处:一个是针对定义方,代码中减少了两个接口类的定义;另一个是对于调用方来说,代码也会更加简洁。就大大减少了代码量,提高了代码可读性,并通过减少类的数量,提高了代码的性能。


使用高阶函数对比

高阶函数的相关概念

函数类型

函数类型(Function Type)就是函数的类型
Kotlin中,函数是一等公民(first class),这意味着函数可以被存储在变量或者数据结构中,它是有类型的。Kotlin使用函数类型来描述一个函数的具体类型。一个完整语法的函数类型如下:

//(x:Int, y:Int) -> Int  这个就是 sum 函数的类型
//    ↑      ↑       ↑
fun sum(a: Int, b: Int):Int{
  return (a+b)
}

(Int, Int) ->Int 就代表了参数类型是两个 Int,返回值类型为 Int 的函数类型。

函数引用

函数的引用,普通的变量也有引用的概念,我们可以将一个变量赋值给另一个变量。而这一点,在函数上也是同样适用的,函数也有引用,并且也可以赋值给变量。如:作为参数传递给高阶函数。

//函数赋值给变量                  函数引用
//     ↑                            ↑
val function: (Int, Int) ->Int = ::sum

高阶函数

定义:高阶函数是将函数用作参数或返回值的函数。
其实Android里的点击事件监听用Kotlin来实现的话,它就是一个高阶函数。

//                    函数作为参数的高阶函数
//                              ↓
fun setOnClickListener(l: (View) -> Unit) { ... }

//                   返回值是函数类型的高阶函数
//                              ↓
fun higherFunction(): (Int, Int) -> Int { ... }

综上可以理解一个函数的参数或是返回值,它们当中有一个是函数的情况下,这个函数就是高阶函数。

Lambda

上面讲到函数的引用这种方法可以给函数类型赋值,也可以通过Lambda表达式对一个函数类型的变量进行赋值(大多数情况都是使用Lambda表达式来调用高阶函数)如下所示:

val function: (Int, Int) ->Int = {num1: Int, num2: Int -> num1 + num2}

Lambda 表达式语法结构:{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体} 函数体中可以编写任意行代码,最后一行代码会自动作为 Lambda 表达式的返回值

高阶函数的调用示例

这里用forEach函数来举例,forEach函数也是一个高阶函数。源码如下:

public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
    for (element in this) action(element)

intArray.forEach(?) //此处? 是个函数类型的参数,函数类型是 (Int) -> Unit
//函数类型的参数是可以定义相同的函数类型的变量传给forEach,如下:
val action: (Int) -> Unit = ??
fun main() {
    intArray.forEach(action)
}

上述我们讲到了函数赋值可以使用函数引用也可以使用 Lambda 表达式,使用函数引用代码如下:

val action: (Int) -> Unit = ::printValue

fun main() {
    intArray.forEach(action)
}

fun printValue(value: Int): Unit {
    println(value)
}

函数引用的方式调用高阶函数太过麻烦,还需要特地写一个函数来调用高阶函数。所以我们绝对多数情况都是使用Lambda 表达式来调用高阶函数的,且Lambda 表达式有很多简洁的写法。
使用Lambda 表达式来改写上述代码:

val action: (Int) -> Unit = {value: Int -> println(value)}

fun main() {
    intArray.forEach(action)
}

1、Kotlin 有类型推到机制,所以 Int 可以去掉

val action: (Int) -> Unit = {value -> println(value)}

2、Lambda 表达式如果只有一个参数,可以直接用 it 来代替,并且不需要声明参数名

val action: (Int) -> Unit = {println(it)}
//将简化后的代码代入,现在上述的代码就变成如下这样
fun main() {
    intArray.forEach({println(it)})
}

3、当Lambda 参数是函数的最后一个参数时,可以将 Lambda 表达式移到函数括号的外面。

fun main() {
    intArray.forEach(){
        println(it)
    }
}

4、Lambda 表达式是函数的唯一一个参数的话,还可以将函数的括号省略

fun main() {
    intArray.forEach{
        println(it)
    }
}

带接收者的函数类型

这里拿 Kotlin 的标准函数 apply函数和also函数来分析:

public inline fun <T> T.apply(block: T.() -> Unit): T {    
    contract {        
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)    
    }    
    block()    
    return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {    
    contract {        
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)    
    }    
    block(this)    
    return this
}

仔细⽐较这两个扩展函数会发现它们⾮常相似:它们都接收⼀个 block 函数并返回 this 值。
唯⼀的差别就在于apply的 block 函数类型为:T.() -> Unit,⽽also的 block 函数类型为:(T) -> Unit)

T.() -> Unit和(T) -> Unit) 区别

看下图apply和also使用可以得出:
also方法中接收到的是it,而在apply方法中接收到的是this

总结

Kotlin官方的源代码Standard.Kt可以去分析其中的 with、let、also、takeIf、repeat、apply,来进一步加深对高阶函数的理解。还有Collections.Kt,可以去分析其中的 map、flatMap、fold、groupBy 等操作符,从而对高阶函数的应用场景有一个更具体的认知。

关键字 inline「内联函数」

Kotlin 中新增了「内联函数」,内联函数起初是在 C++ 里面的。当一个函数被内联 inline 标注后,在调用它的地方,会把这个函数方法体中的所以代码移动到调用的地方,而不是通过方法间压栈进栈的方式。

内联函数可以消除Lambda表达式运行时带来的额外内存开销


参考

朱涛 · Kotlin 编程第一课
Kotlin 高阶函数详解

上一篇 下一篇

猜你喜欢

热点阅读