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))
相关推荐
还鮟3 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡4 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi004 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil6 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你6 小时前
Android View的绘制原理详解
android
移动开发者1号9 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号9 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best14 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk14 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭18 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin