Kotlin 2.1.0 入门教程(七)

高阶函数和 lambda 表达式

Kotlin 函数是一等公民,这意味着它们可以存储在变量和数据结构中,并且可以作为参数传递给其他高阶函数或从其他高阶函数返回。您可以对函数执行任何适用于其他非函数值的操作。

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

kotlin 复制代码
fun main() {
    // 定义一个高阶函数。
    fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
        return operation(a, b)
    }

    // 使用 lambda 表达式作为参数。
    val sum = operateOnNumbers(3, 4) { x, y -> x + y }
    println("3 + 4 = $sum") // 3 + 4 = 7
}

高阶函数

高阶函数是接受函数作为参数或返回函数的函数。

高阶函数的一个很好的例子是函数式编程中的 fold 惯用法,用于集合。它接受一个初始累加器值和一个组合函数,并通过依次将当前累加器值与每个集合元素组合来构建其返回值,每次替换累加器值:

kotlin 复制代码
fun <T, R> Collection<T>.fold(
    initial: R,
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

使用:

kotlin 复制代码
fun main() {
    val list = listOf(2, 3, 4)
    
    /* 
     * acc = 0, item = 2, acc + item
     * acc = 2, item = 3, acc + item
     * acc = 5, item = 4, acc + item
     */
    list.fold(0) { acc, item ->
        println("acc = $acc, item = $item, acc + item");
        acc + item
    }
    
    /*
     * acc = 1, item = 2, acc * item
     * acc = 2, item = 3, acc * item
     * acc = 6, item = 4, acc * item
     */
    list.fold(1) { acc: Int, item: Int ->
        println("acc = $acc, item = $item, acc * item");
        acc * item
    }
}

函数类型

Kotlin 使用函数类型,例如 (Int) -> String,来处理与函数相关的声明。

这些类型具有与函数签名(参数和返回值)对应的特殊表示法:

  • 所有函数类型都有一个带括号的参数类型列表和一个返回类型 (A, B) -> C 表示一个类型,该类型表示接受类型为 AB 的两个参数并返回类型为 C 的值的函数。参数类型列表可以为空,例如 () -> AUnit 返回类型不能省略。

  • 函数类型可以选择性地具有一个额外的接收者类型,该类型在表示法中位于点之前:类型 A.(B) -> C 表示可以在接收者对象 A 上调用的函数,该函数接受参数 B 并返回值 C

  • 挂起函数属于一种特殊的函数类型,其表示法中带有 suspend 修饰符,例如 suspend () -> Unitsuspend A.(B) -> C

kotlin 复制代码
fun main() {
    val func1: () -> Unit = { println("click") }
    val func2: () -> Unit = fun () { println("press") }
    
    func1() // click
    func2() // press
}
kotlin 复制代码
fun main() {
    val func1: (String) -> String = {
        name -> "Hello, $name!"
    }
    println(func1("Kotlin")) // Hello, Kotlin!

    val func2: String.(String) -> String = {
        other -> "Hello, $this and $other!"
    }
    println("Alice".func2("Bob")) // Hello, Alice and Bob!
}

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

要指定函数类型为可空,请使用括号:((Int, Int) -> Int)?

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

kotlin 复制代码
fun main() {
    val nullableFunction: ((Int, Int) -> Int)? = { x, y -> x + y }
    val combinedFunction: (Int) -> ((Int) -> Unit) = {
        x -> {
            y -> println(x + y)
        }
    }

    println("${nullableFunction?.invoke(3, 4)}") // 7
    combinedFunction(3)(4) // 7
}

箭头表示法是右结合的,(Int) -> (Int) -> Unit 等同于前面的示例,但与 ((Int) -> (Int)) -> Unit 不同。

kotlin 复制代码
fun main() {
    val func: (Boolean) -> (Int) -> Boolean = { bool -> {
        x ->
        	println("x = $x, bool = $bool")
        	bool
    }}
    
    // x = 0, bool = false
    // false
    println(func(false)(0))
    
    val func2: (Boolean) -> ((Int) -> Boolean) = { bool -> {
        x ->
        	println("x = $x, bool = $bool")
        	bool
    }}
    
    // x = 1, bool = true
    // true
    println(func2(true)(1))
}
kotlin 复制代码
fun main() {
    val func: (Boolean) -> (Int) -> Boolean = fun (bool: Boolean): (Int) -> Boolean {
        return fun (x: Int): Boolean {
            println("x = $x, bool = $bool")
            return bool
        }
    }
    
    // x = 1, bool = false
	// false
   	println(func(false)(1))
}

还可以使用类型别名为函数类型提供替代名称。

kotlin 复制代码
typealias ClickHandler = (Button, ClickEvent) -> Unit

实例化函数类型

有几种方法可以获取函数类型的实例:

  • 使用函数字面量中的代码块,采用以下形式之一:

    • lambda 表达式:{ a, b -> a + b }

    • 匿名函数:fun (s: String): Int { return s.toIntOrNull() ?: 0 }

  • 带有接收者的函数字面量可以用作带有接收者的函数类型的值。

  • 使用对现有声明的可调用引用:

    • 顶层、局部、成员或扩展函数:::isOddString::toInt

    • 顶层、成员或扩展属性:List<Int>::size

    • 构造函数:::Regex

  • 这些包括绑定指向特定实例成员的可调用引用:foo::toString

  • 使用实现函数类型作为接口的自定义类的实例。

kotlin 复制代码
fun main() {
    // 定义一个带有接收者的函数类型 StringBuilder.(String) -> Unit。
    val appendText: StringBuilder.(String) -> Unit = {
        // this 指向 StringBuilder 对象。
        this.append(it)
    }

    val sb = StringBuilder()
    sb.appendText("Hello, ")
    sb.appendText("Kotlin!")

    println(sb.toString()) // Hello, Kotlin!
}
kotlin 复制代码
fun main() {
    val appendText: StringBuilder.(String) -> Unit = { str -> this.append(str) }

    val sb = StringBuilder()
    sb.appendText("Hello, ")
    sb.appendText("Kotlin!")

    println(sb.toString()) // Hello, Kotlin!
}
kotlin 复制代码
// 顶层函数。
fun isOdd(n: Int): Boolean = n % 2 != 0

fun main() {
    // 引用顶层函数。
    val func1: (Int) -> Boolean = ::isOdd

    println(func1(3)) // true
    println(func1(4)) // false
    
    // 局部函数。
    fun localFunc(n: Int): Boolean {
        return if (n == 1) true else false
    }
    
    // 引用局部函数。
    val func2: (Int) -> Boolean = ::localFunc
    
    println(func2(1)) // true
}
kotlin 复制代码
// 扩展函数。
fun String.myToInt(): Int {
    return this.toInt()
}

fun main() {
    // 引用成员函数。
    val func1: String.() -> Int = String::toInt

    println("123".func1()) // 123
    
    // 引用扩展函数。
    val func2: String.() -> Int = String::myToInt

    println("123".func2()) // 123
}
kotlin 复制代码
// 顶层属性。
val pi: Int = 10

// 扩展属性。
val String.isPalindrome: Boolean
    get() = this == this.reversed()

fun main() {
    // 引用顶层属性。
    val myPi: () -> Int = ::pi
    
    // 引用成员属性。
    val myLength: (String) -> Int = String::length
    
    // 引用扩展属性。
    val myPalindrome: (String) -> Boolean = String::isPalindrome
    
    println(myPi()) // 10
    println(myLength("abc")) // 3
    println(myPalindrome("abc")) // false
}
kotlin 复制代码
fun main() {
    // 引用 String 的无参构造函数。
    val stringConstructor: () -> String = ::String

    // 调用构造函数。
    val emptyString = stringConstructor()
    println("Empty string: '$emptyString'") // Empty string: ''
}
kotlin 复制代码
fun main() {
    // 引用 String 的 CharArray 构造函数。
    val stringConstructor: (CharArray) -> String = ::String

    // 调用构造函数。
    val charArray = charArrayOf('H', 'e', 'l', 'l', 'o')
    val str = stringConstructor(charArray)
    println("String from char array: '$str'") // String from char array: 'Hello'
}
kotlin 复制代码
class Foo {
    override fun toString(): String = "Foo"
}

fun main() {
    val foo = Foo()

    // 绑定可调用引用 foo::toString。
    val toStringGetter: () -> String = foo::toString

    println(toStringGetter()) // Foo
}
kotlin 复制代码
class IntTransformer: (Int) -> Int {
    override operator fun invoke(x: Int): Int = TODO()
}

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

带有和不带有接收者的函数类型的非字面量值可以互换,因此接收者可以代替第一个参数,反之亦然。

例如,类型为 (A, B) -> C 的值可以在需要类型为 A.(B) -> C 的值的地方传递或赋值,反之亦然。

kotlin 复制代码
fun main() {
    // 普通函数类型。
    val add: (Int, Int) -> Int = { a, b -> a + b }

    // 带有接收者的函数类型。
    val addWithReceiver: Int.(Int) -> Int = { other -> this + other }

    // 互相赋值。
    val addAsReceiver: Int.(Int) -> Int = add
    val addAsNormal: (Int, Int) -> Int = addWithReceiver

    println("${add(3, 4)}") // 7
    println("${3.addWithReceiver(4)}") // 7
}
kotlin 复制代码
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun

fun runTransformation(f: (String, Int) -> String): String {
    return f("hello", 3)
}
val result = runTransformation(repeatFun)

默认情况下,推断的函数类型没有接收者,即使变量是用扩展函数的引用初始化的。要改变这一点,请显式指定变量类型。

kotlin 复制代码
fun main() {
    // 默认情况下,推断的函数类型没有接收者。
    val toInt: (String) -> Int = String::toInt

    // 显式指定变量类型。
    val toIntExplicit: String.() -> Int = String::toInt
}

调用函数类型实例

函数类型的值可以通过其 invoke(...) 操作符调用:f.invoke(x) 或直接 f(x)

如果该值具有接收者类型,则应将接收者对象作为第一个参数传递。

另一种调用带有接收者的函数类型值的方法是在其前面加上接收者对象,就像该值是扩展函数一样:1.foo(2)

kotlin 复制代码
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus

println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))

println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3))
相关推荐
MiyamuraMiyako1 小时前
从 0 到发布:Gradle 插件双平台(MavenCentral + Plugin Portal)发布记录与避坑
android
NRatel1 小时前
Unity 游戏提升 Android TargetVersion 相关记录
android·游戏·unity·提升版本
叽哥4 小时前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
风往哪边走4 小时前
创建自定义语音录制View
android·前端
用户2018792831674 小时前
事件分发之“官僚主义”?或“绕圈”的艺术
android
用户2018792831674 小时前
Android事件分发为何喜欢“兜圈子”?不做个“敞亮人”!
android
Kapaseker6 小时前
你一定会喜欢的 Compose 形变动画
android
QuZhengRong6 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
zhangphil7 小时前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
android·kotlin
程序员码歌14 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端