kotlin高阶函数,看完你就能学会

前言

今天我们来聊下 lambda 表达式。lambda 表达式应该都不陌生,在 Java8 中引入一个很重要的特性,将开发者从原来繁琐的语法中解放出来,但是局限于只有 Java8 版本才能使用。而 Kotlin 弥补了这一问题,Kotlin 中的 lambda 表达式与 Java 混合编程可以支持 Java8 以下的版本。

为什么使用 Kotlin 的 Lambda 表达式

针对 Kotlin 中使用 lambda 表达式的问题,主要有一下几点优点:

  • Kotlin 的 lambda 表达式更加简洁易懂的语法实现功能,使开发者从原有冗余啰嗦的语法声明中解放出来。可以使用函数式编程中的过滤、映射、转换等操作符处理集合数据,从而使你的代码更加接近函数式编程的风格。
  • Java8 以下的版本不支持 lambda 表达式,而 Kotlin 则兼容 Java8 以下版本有很好的互操作性,非常适合 Java8 以下版本与 Kotlin 混合开发的模式,解决 Java8 以下版本不能使用 lambda 表达是瓶颈。
  • 在 Java8 版本使用 lambda 表达式是有限制的,它不是真正意义上的闭包,而 Kotlin 中的 lambda 才是真正意义上支持闭包的实现。(下面会阐述)

Kotlin 函数是一级函数,这意味着它们可以存储在变量和数据结构中,作为参数传递给其他高阶函数,也可以从其他高阶函数返回。对于其他非函数值,你可以以任何可能的方式对函数进行操作。

为了实现这一点,作为一种静态类型编程语言,Kotlin 使用一系列函数类型来表示函数,并提供了一组专门的语言构造,比如 lambda 表达式。

一、高阶函数

高阶函数是指接收函数作为参数(或返回函数)的函数。即一个函数可以将另一个函数当作参数(或返回函数),将其他函数用作参数(或返回函数)的函数被称为“高阶函数”。

(1)下面是一个高阶函数的示例:

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

stringMapper() 函数的参数是一个 String 以及一个函数,这个参数是将根据你传递给它的 String 来推导 Int 值。

要调用 stringMapper(),可以传递一个 String 和一个满足第二个参数条件的函数(即一个将 String 当作输入并输出 Int 的函数),如下示例:

stringMapper("Android", { input ->
    input.length
})

如果匿名函数是在某个函数上定义的最后一个参数,则你可以在用于调用该函数的圆括号 () 之外传递这个参数,如下所示:

stringMapper("Android") { input ->
    input.length
}

(2)另一个很好的例子就是针对集合的 函数式编程习语折叠,它接受一个初始的累加器值和一个组合函数,并通过连续地将当前的累加器值与每个集合元素组合来构建它的返回值,替换这个叠加器:

//从[初始]值开始累加值,从左到右对当前累加器值和每个元素应用[操作]
fun <T, R> Collection<T>.fold(
    initial: R,
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

在上面的代码中,参数 combine 有一个函数类型 (R, T) -> R ,因此它接收一个函数,该函数接收两个参数类型 RT,并返回一个类型 R 的值。它在 for 循环中调用,然后将返回值分配给 accumulator

调用 fold,我们需要给它传递一个函数类型的实例作为参数,lambda表达式(在下面描述的更详细)在高阶函数调用站点被广泛用于这种情况:

val items = arrayOf(1, 2, 3, 4, 5)
//Lambdas是大括号括起来的代码块
items.fold(0, { acc: Int, i: Int -> //当lambda 有参数时,参数在前面,然后是 `->` 符号
    print("acc == $acc | i == $i | ")
    val result = acc + i
    println("result == $result")
    //如果不显式指定,lambda 中的最后一个表达式被认定为是返回值
    result
})

//参数类型在lambda中如果能自动推断则可以省略
val itemsStr = items.fold("Elements:", { acc, i -> acc + "" + i })

//函数引用也可以用于高阶函数调用
val products = items.fold(1, Int::times)

打印数据如下:

acc == 0 | i == 1 | result == 1
acc == 1 | i == 2 | result == 3
acc == 3 | i == 3 | result == 6
acc == 6 | i == 4 | result == 10
acc == 10 | i == 5 | result == 15

1.1 函数类型

Kotlin 使用一系列函数类型,比如 (Int) -> String 来声明函数:val onClick: () -> Unit = ...。这些类型有一个特殊的符号,对应函数的签名,即它们的参数和返回值:

  • (A, B) -> C :  所有的函数类型有一个带括号的参数类型列表和一个返回类型。(A, B) -> C 表示一个函数类型,该类型表示具有类型 A 和 B 的两个参数并返回类型 C 的值的函数。参数类型列表可以是空的,如 () -> A。不能省略 Unit 返回类型;
  • A.(B) -> C :  函数类型可以有一个附加的接收类型,它在点符号 . 之前指定。类型 A.(B) -> C 表示可以在接收对象 A 调用 B 类型的参数,返回值为 C 类型的函数。带有接收器的函数文字通常与这些类型一起使用;
  • 挂起函数 :  挂起函数属性特性类型的函数,在表示法中有一个挂起修饰符。例如:suspend () -> Unit 或者 suspend A.(B) -> C

函数类型表示法可以选择包括函数参数的名称:(x: Int, y: Int) -> Point。这些名称可用于记录参数的含义。

1.要指定一个函数类型可以为空,可以使用括号:((Int, Int) -> Int)?

2.函数类型可以使用括号组合:(Int) -> ((Int) -> Unit)

3.箭头符号 -> 是右结合的,(Int) -> (Int) -> Unit(Int) -> ((Int) -> Unit) 表示同一个类型,但是与函数类型 ((Int) -> (Int)) -> Unit 不同。

你也可以使用类型别名给一个函数类型一个代替名称:

typealias ClickHandler = (Button, ClickEvent) -> Unit

1.2 实例化函数类型

下面有几种方法可以获得一个函数类型的实例:

(1)在函数文字中使用代码块,形式如下:

  • 一个 lambda 表达式:{ a, b -> a + b }
  • 一个匿名函数:fun(s: String): Int { return s.toIntOrNull() ?: 0 }

带有 receiver 的函数文本可以用作带有 receiver 的函数类型的值。

(2)使用实现函数类型作为接口的自定义类的实例:

//定义IntTransformer类型,实现了 (Int) -> Int 接口
class IntTransformer : (Int) -> Int {
    override operator fun invoke(num: Int): Int = TODO()
}

val intFunction: (Int) -> Int = IntTransformer()

如果数据足够明朗,编译器可以推断出变量的函数类型:

val result = { i: Int -> i * 2 }//推断出的类型是(Int) -> Int

对于包含 receiver 的方法类型,如果 receiver 的类型以及该方法类型的剩余参数类型和没有显式定义 receiver 的方法类型的入参相匹配,则二者可以进行相护赋值。比如方法类型 String.(Int) -> Boolean 就是显式包含 receiver 的方法类型,该 receiver 的方法类型是 String,参数类型是 Int,所以等同于方法类型 (String, Int) -> Boolean,这个方法类型接收一个 String 类型和一个 Int 类型参数,而第一个 String 类型刚好和 receiver 相匹配,剩下的参数类型也相互匹配,可以认为它们是相等的。

//String,Int传入的参数类型后面的String表示返回值类型,times表示Int类型参数
val substringStr: String.(Int) -> String = { times ->
    this.substring(0, times)//times为5
}

val twoParameters: (String, Int) -> String = substringStr

//(A, B) -> C 所有的函数类型有一个带括号的参数类型列表和一个返回类型,
//表示一个类型,该类型表示具有类型 A 和 B 的两个参数并返回类型 C 的值的函数。
fun runTransformation(ss: (String, Int) -> String): String {
    return ss("Android", 5)
}

val trans = runTransformation(substringStr) //substringStr()函数作为参数传递给runTransformation()函数

打印数据如下:

Andro

注意:没有接收方的函数类型在默认情况下会被自动推断出来,即使使用扩展函数的引用初始化变量也是如此。如果要更改,请显式指定变量类型。

1.3 调用函数类型实例

函数类型的值可以通过使用其 invoke(...) 操作符来调用:f.invoke(x) 或者 f(x)invoke() 表示通过 函数变量 调用自身。

如果值具有 receiver 类型,则 receiver 对象应作为第一个参数传递。用 receiver 调用函数类型的值的另一种方法是在它前面加上 receiver 对象,就好像这个值是一个扩展函数:1.foo(2)

//String.plus()通过将该字符串与给定的其他对象的字符串表示形式链接起来而获得的字符串
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus //Int.plus()将另一个值添加到此值

val str = stringPlus.invoke("<-", "->") //打印:<-->
val str2 = stringPlus("Hello", "World") //打印:HelloWorld

val int1 = intPlus(1, 1) //打印:2
val int2 = intPlus.invoke(2, 3) //打印:5
val int3 = 4.intPlus(5) //打印:9

本文在开源项目:Android开发不会这些?如何面试拿高薪中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,扫描下方二维码,免费领取,资源持续更新中…

猜你喜欢

转载自blog.csdn.net/Eqiqi/article/details/129875836