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))
相关推荐
阿巴斯甜9 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker9 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952710 小时前
Andorid Google 登录接入文档
android
黄林晴11 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android