当前位置: 首页 > article >正文

Kotlin 2.1.0 入门教程(七)

高阶函数和 lambda 表达式

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

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

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 惯用法,用于集合。它接受一个初始累加器值和一个组合函数,并通过依次将当前累加器值与每个集合元素组合来构建其返回值,每次替换累加器值:

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
}

使用:

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

fun main() {
    val func1: () -> Unit = { println("click") }
    val func2: () -> Unit = fun () { println("press") }
    
    func1() // click
    func2() // press
}
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)

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 不同。

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))
}
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))
}

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

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

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

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!
}
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!
}
// 顶层函数。
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
}
// 扩展函数。
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
}
// 顶层属性。
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
}
fun main() {
    // 引用 String 的无参构造函数。
    val stringConstructor: () -> String = ::String

    // 调用构造函数。
    val emptyString = stringConstructor()
    println("Empty string: '$emptyString'") // Empty string: ''
}
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'
}
class Foo {
    override fun toString(): String = "Foo"
}

fun main() {
    val foo = Foo()

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

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

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

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

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

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
}
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)

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

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

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

调用函数类型实例

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

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

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

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))

http://www.kler.cn/a/513196.html

相关文章:

  • 学习ASP.NET Core的身份认证(基于JwtBearer的身份认证8)
  • 数据结构学习记录-队列
  • linux如何并行执行命令
  • HDFS的Java API操作
  • 消息队列篇--原理篇--RocketMQ(NameServer,Broker,单机上每秒处理数百万条消息性能)
  • Tesla Free-Fall Attack:特斯拉汽车网络安全事件纪要
  • 若依报错:无法访问com.ruoyi.common.annotation
  • 微信小程序使用picker根据接口给的省市区的数据实现省市区三级联动或者省市区街道等多级联动
  • 在Android Studio中如何实现综合实验MP3播放器(保姆级教程)
  • Java学习,List 元素替换
  • 服务器安装ESXI7.0系统及通过离线包方式升级到ESXI8.0
  • Y3编辑器功能指引
  • Redis单线程为什么能这么快
  • grafana+prometheus监控linux指标
  • 美区TikTok解封后如何回归使用?
  • 软件授权产品介绍
  • 算法题目总结-栈和队列
  • 数据库基础知识:理论、E-R图、事务、原则
  • 【VOS源码解析-2024CVPR-Cutie】1、train_wrapper结构解析
  • sqlmap 自动注入 -01
  • 【Linux】华为服务器使用U盘安装统信操作系统
  • 跨境电商之小程序shinecrys水晶国度小程序数据分析
  • 【HF设计模式】06-命令模式
  • Flink底层架构与运行流程
  • 2.4 kubectl命令行设置7大命令分组
  • 三轴云台之跟随模式篇