学习Kotlin语法(三)

简介

在上一节,我们对Kotlin中面向对象编程(OOP)的相关知识有了大致的了解,本章节我们将去进一步了解函数、lambada表达式、内联函数、操作符重载、作用域函数。

目录

  1. 函数
  2. 高阶函数和Lambda
  3. 内联函数

1.函数

  • 函数的使用

    Kotlin 函数使用关键字 fun 声明:

    kotlin 复制代码
    fun double(x: Int): Int {
        return 2 * x
    }
    fun main() {
        val result = double(2)
        println("result: $result") // 输出为 result: 4
    
        println("result: ${double(3)}") // 输出为 result: 6
    }
    • fun : 定义函数的关键字
    • double : 函数名
    • x: Int : 定义的整型参数,参数名 x 在前,参数类型 Int 在后
    • Int : 定义函数的返回类型为 Int
    • { return 2 * x } : 函数体,double 函数的具体内容
    • 使用标准方法 val result = double(2) 调用了 double 函数
    • 函数有返回值,直接调用函数返回 result: ${double(3)}
  • 参数 函数参数使用 Pascal 符号定义 -名称:类型。参数使用逗号分隔,并且每个参数必须明确输入:

    kotlin 复制代码
    fun powerOf(number: Double, exponent: Int): Double {
        return number.pow(exponent)
    }
    
    fun main() {
        println(powerOf(3.0,3)) // 输出为 27.0
    }
  • 默认参数 函数参数可以有默认值,当跳过相应的参数时会使用这些默认值。这减少了重载的次数:

    kot 复制代码
    fun powerOf(number: Double, exponent: Int = 3): Double {
        return number.pow(exponent)
    }
    
    fun main() {
        println(powerOf(2.0)) // 输出为 8.0
    }

    使用 = 给参数设置默认值

    重写方法始终使用基方法的默认参数值。重写具有默认参数值的方法时,必须从签名中省略默认参数值:

    kotlin 复制代码
    open class Parent {
        open fun greet(name: String = "Guest") {
            println("Hello, $name!")
        }
    }
    
    class Child : Parent() {
        override fun greet(name: String = "Guest") { // 编译器报错 不允许重写函数为其参数指定默认值
            println("Hi, $name!")
        }
    }
    
    fun main() {
        val parent: Parent = Child()
        parent.greet() // 输出: Hello, Guest!
    }

    如果默认参数位于没有默认值的参数之前,则只能通过调用带有命名参数的函数来使用默认值:

    kotlin 复制代码
    class Parent {
        fun greet(name: String = "Guest", age: Int) {
            println("Hello, $name! you age is $age")
        }
    }
    fun main() {
        val parent = Parent()
        parent.greet(age = 22) // Hello, Guest! you age is 22
    }

    如果默认参数后的最后一个参数是lambda,则可以将其作为命名参数传递,也可以在括号外传递:

    kotlin 复制代码
    class Parent {
        fun greet(name: String = "Guest", age: Int, out: (name: String, age: Int) -> Unit) {
            out(name, age)
        }
    }
    
    fun main() {
        val parent = Parent()
        parent.greet(age = 22, out = {name, age ->
            println("Hello $name, you age is $age") // 输出为 Hello Guest, you age is 22
        })
        parent.greet(age = 22) { name, age ->
            println("Hello $name, you age is $age") // 输出为 Hello Guest, you age is 22
        }
    }
  • 命名参数

    调用函数时,我们可以命名一个或多个函数参数。当函数具有许多参数且难以将值与参数关联时(尤其是当参数为布尔值或null值时) ,这会很有用。

    当在函数调用中使用命名参数时,我们可以自由更改它们列出的顺序。如果想使用它们的默认值,可以完全省略这些参数。

    kotlin 复制代码
    class Parent {
        fun greet(name: String, age: Int = 22, isMarry: Boolean = false) {
            println("Hello $name, you age is $age, isMarry is $isMarry")
        }
    }
    fun main() {
        val parent = Parent()
        // 跳过所有默认值
        parent.greet("Lucy") // 输出为 Hello Lucy, you age is 22, isMarry is false
        // 还可以跳过具有默认值的特定参数,而不是省略所有参数。但是,在第一个跳过的参数之后,必须命名所有后续参数:
        parent.greet("June", isMarry = true) // 输出为 Hello June, you age is 22, isMarry is true
    }
  • 返回单位的函数

    如果函数没有返回有用的值,则其返回类型为UnitUnit是只有一个值的类型 - Unit。此值不必明确返回:

    kotlin 复制代码
    fun printHello(name: String?): Unit { // : Unit 可以省略
        if (name != null)
            println("Hello $name")
        else
            println("Hi there!")
    }
    fun main() {
        printHello(null) // 输出 Hi there!
    }
  • 单表达式函数

    当函数体由单个表达式组成时,可以省略花括号并在=符号后指定函数体, 当编译器可以推断出返回类型时,明确声明返回类型是可选的: :

    kotlin 复制代码
    fun double(x: Int): Int = x * 2
    fun double2(x: Int) = x * 2
    fun main() {
        println(double(3)) // 输出为 6
        println(double2(3)) // 输出为 6
    }
  • 可变数量的参数

    在 Kotlin 中,可变参数(Varargs) 允许函数接受任意数量的同一类型的参数。这是通过 vararg 关键字实现的。可变参数在需要处理不确定数量的输入时非常有用,例如处理一组数字、字符串或其他类型的数据

    kotlin 复制代码
    fun printNumbers(vararg numbers: Int) {
        for (number in numbers) {
            print(number)
        }
    }
    fun <T> asList(vararg ts: T): List<T> {
        val result = ArrayList<T>()
        for (t in ts) // ts is an Array
            result.add(t)
        return result
    }
    fun main() {
        printNumbers(1, 2, 3) // 输出: 1 2 3
        println()
        printNumbers(4, 5, 6, 7, 8) // 输出: 4 5 6 7 8
        println()
        println(asList(9, 10, 11)) // 输出: [9, 10, 11]
    }

    在函数内部,vararg类型的 -参数T可视为 的数组T,如上例所示,其中ts变量的类型为Array。

    只有一个参数可以标记为vararg。如果vararg参数不是列表中的最后一个,则可以使用命名参数语法传递后续参数的值,或者,如果参数具有函数类型,则通过在括号外传递 lambda。

    调用函数时vararg,可以单独传递参数,例如arrayOf(3, 4, 5, 6, 7, 8)。如果已经有一个数组并希望将其内容传递给函数,请使用展开运算符(在数组前加上*):

    kotlin 复制代码
    fun <T> asList(vararg ts: T): List<T> {
        val result = ArrayList<T>()
        for (t in ts) // ts is an Array
            result.add(t)
        return result
    }
    
    fun main() {
        val a = arrayOf(3, 4, 5, 6, 7, 8)
        println(asList(1, 2, 3, *a, 9, 10, 11)) // 输出: [1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    }
  • 中缀表达式

    标有关键字的函数infix也可以使用中缀表示法(省略调用时的点和括号)来调用。中缀函数必须满足以下要求:

    • 它们必须是成员函数或者扩展函数。

    • 它们必须有一个参数。

    • 该参数不能接受可变数量的参数,并且不能有默认值。

    kotlin 复制代码
    class Person(val name: String) {
        infix fun sayHelloTo(other: Person) {
            println("$name says hello to ${other.name}")
        }
    }
    fun main() {
        val alice = Person("Alice")
        val bob = Person("Bob")
        alice sayHelloTo bob // 输出: Alice says hello to Bob
    }
  • 本地函数

    在 Kotlin 中,本地函数(Local Functions) 是指定义在另一个函数内部的函数。这种函数的作用域仅限于其外部函数,无法在其他地方调用。本地函数可以访问外部函数的变量和参数:

    kotlin 复制代码
    fun main() {
        val name = "NPC:"
    
        fun printMessage(message: String) {
            println("$name$message")
        }
        printMessage("Welcome!") // 输出 NPC:Welcome!
    }

    本地函数可以修改外部变量:

    kotlin 复制代码
    fun main() {
        var count = 0
    
        fun increment() {
            count++
            println("Count: $count")
        }
    
        increment() // 输出 Count: 1
        increment() // 输出 Count: 2
        println("Count: $count") // 输出 Count: 2
    }

    本地函数非常适用于封装重复逻辑或分解复杂操作:

    kotlin 复制代码
    fun main() {
        // 本地函数
        fun calculateTotal(price: Double, quantity: Int): Double {
            // 计算折扣
            fun getDiscount(): Double {
                return if (quantity > 5) price * 0.1 else 0.0
            }
    
            // 计算税费
            fun getTax(subtotal: Double): Double {
                return subtotal * 0.08
            }
    
            val discount = getDiscount()
            val subtotal = (price * quantity) - discount
            return subtotal + getTax(subtotal)
        }
    
        val total = calculateTotal(100.0, 6)
        println("总金额: $total") // 输出 总金额: 637.2
    }
  • 成员函数

    Kotlin 中的 成员函数(Member Functions) 是定义在类或对象内部的函数,属于类的一部分。它们用于描述类的行为或操作类的数据(属性),是面向对象编程(OOP)的核心组成部分

    基本语法:

    kotlin 复制代码
    class Person(val name: String) {
        // 成员函数
        fun greet() {
            println("Hello, my name is $name")
        }
    }
    
    fun main() {
        val person = Person("Alice")
        person.greet() // 输出: Hello, my name is Alice
    }
  • 访问类的属性

    成员函数可以直接访问类的属性和其他成员(包括私有成员):

    kotlin 复制代码
    class Car(val brand: String, private var speed: Int = 0) {
        fun accelerate(amount: Int) {
            speed += amount // 访问私有属性
            println("加速到: $speed km/h")
        }
        fun getSpeed(): Int {
            return speed
        }
    }
    
    fun main() {
        val car = Car("Tesla")
        car.accelerate(50) // 输出: 加速到 50 km/h
        println("当前速度: ${car.getSpeed()}") // 输出: 当前速度: 50
    }
  • 继承与重写

    如果类被标记为 open,其成员函数可以被继承和重写:

    kotlin 复制代码
    open class Animal {
        open fun makeSound() {
            println("动物发出声音")
        }
    }
    
    class Dog : Animal() {
        override fun makeSound() {
            println("汪汪汪!")
        }
    }
    
    fun main() {
        val dog = Dog()
        dog.makeSound() // 输出: 汪汪汪!
    }
  • 函数重载

    成员函数支持重载(相同函数名,不同参数列表):

    kotlin 复制代码
    class Calculator {
        fun add(a: Int, b: Int): Int = a + b
        fun add(a: Double, b: Double): Double = a + b
    }
    
    fun main() {
        val calc = Calculator()
        println(calc.add(3, 5))   // 输出: 8
        println(calc.add(3.5, 2.5)) // 输出: 6.0
    }
  • 泛型函数

    Kotlin 中的 泛型函数(Generic Functions) 允许你编写可以处理多种数据类型的代码,同时保持类型安全性。通过泛型,你可以避免重复编写针对不同类型但逻辑相同的函数

    基本语法

    泛型函数通过在函数名后使用尖括号 声明类型参数(T 是占位符,可替换为任意标识符)。类型参数 T 可以在函数的参数、返回类型或函数体内使用。

    kotlin 复制代码
    // 泛型函数示例: 交换两个元素的值
    fun <T> swap(a: T, b: T): Pair<T, T> {
        return Pair(b, a)
    }
    fun main() {
        val swapped = swap(10, 20) // 类型判断为 Int
        println(swapped) // 输出: (20, 10)
    
        val swappedStr = swap("A", "B") // 类型判断为 String
        println(swappedStr) // 输出: (B, A)
    }

    类型约束

    通过 where 子句或 : 指定泛型类型的约束(如必须是某个类的子类或实现某个接口)。

    kotlin 复制代码
    // 要求 T 必须实现 Comparable 接口
    fun <T: Comparable<T>> max(a: T, b: T): T {
        return if (a > b) a else b
    }
    fun main() {
        println(max(3, 5)) // 输出: 5
        println(max("X", "Y")) // 输出: Y
    }

    多类型参数

    可声明多个泛型类型参数:

    kotlin 复制代码
    fun &lt;K, V&gt; toMap(key: K, value: V): Map&lt;K, V&gt; {
        return mapOf(key to value)
    }
    
    fun main() {
        val map = toMap(1, &quot;Apple&quot;)
        println(map) // 输出: {1=Apple}
    }x
  • 型变(Variance)

    Kotlin 通过 out(协变)和 in(逆变)控制泛型类型的继承关系:

    协变(out):允许子类型替代父类型(适用于生产者)。

    kotlin 复制代码
    // 协变示例:返回泛型类型
    fun <T> copyData(source: List<out T>, destination: MutableList<T>) {
        destination.addAll(source)
    }
    fun main() {
        // 源列表(子类型元素)
        val intList: List<Int> = listOf(1, 2, 3)
    
        // 目标列表(父类型容器)
        val numberList: MutableList<Number> = mutableListOf(10.5, 20.7)
    
        // 将 Int 列表复制到 Number 列表中
        copyData(intList, numberList)
    
        println(numberList) // 输出: [10.5, 20.7, 1, 2, 3]
    }

    逆变(in):允许父类型替代子类型(适用于消费者)

    kotlin 复制代码
    // 逆变示例:消费泛型类型
    fun <T> fillList(destination: MutableList<in T>, value: T) {
        destination.add(value)
    }
    
    fun main() {
        // 目标列表(父类型容器)
        val anyList: MutableList<Any> = mutableListOf("Hello", 100)
    
        // 向 anyList 中添加 String 类型元素
        fillList(anyList, "Kotlin")
    
        println(anyList) // 输出: [Hello, 100, Kotlin]
    }
  • 尾递归函数

    Kotlin 中的 尾递归函数(Tail Recursive Functions) 是一种特殊的递归形式,通过编译器优化可以避免递归调用时的栈溢出问题

    尾递归:函数的最后一个操作是递归调用自身(即递归调用后没有其他计算)。

    优化原理:Kotlin 编译器会将尾递归转换为等效的循环(while 或 for),从而避免递归调用栈的累积。

    普通递归

    kotlin 复制代码
    fun factorial(n: Int): Int {
        if (n == 0) return 1
        return n * factorial(n - 1) // 递归调用后还有乘法操作,不是尾递归
    }
    
    fun main() {
        println(factorial(5))  // 输出: 120
        println(factorial(10000)) // 栈溢出错误!
    }

    尾递归

    kotlin 复制代码
    tailrec fun factorialTailRec(n: Int, acc: Int = 1): Int {
        if (n == 0) return acc
        return factorialTailRec(n - 1, acc * n) // 最后一个操作是递归调用
    }
    
    fun main() {
        println(factorialTailRec(5))     // 输出: 120
        println(factorialTailRec(10000)) // 正常计算,无栈溢出
    }

    应用场景:

    • 计算斐波那列数列

      kotlin 复制代码
      tailrec fun fibonacci(n: Int, a: Int = 0, b: Int = 1): Int {
          return when (n) {
              0 -> a
              1 -> b
              else -> fibonacci(n - 1, b, a + b)
          }
      }
      
      fun main() {
          println(fibonacci(10)) // 输出: 55
      }
    • 遍历链表

      kotlin 复制代码
      data class Node(val value: Int, val next: Node?)
      
      tailrec fun sumNodes(node: Node?, acc: Int = 0): Int {
          return if (node == null) acc
          else sumNodes(node.next, acc + node.value)
      }
      
      fun main() {
          val list = Node(1, Node(2, Node(3, null)))
          println(sumNodes(list)) // 输出: 6
      }

2.高阶函数和Lambda

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

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

  • 高阶函数

    高阶函数是一种将函数作为参数或返回函数的函数。

    高阶函数的一个很好的例子是集合的函数式编程习惯用法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
    }
    fun main() {
        val numbers = listOf(1, 2, 3, 4)
        // 求和
        val sum = numbers.fold(0){ acc, num -> acc + num}
        println(sum) // 输出: 10
        // 求积
        val product = numbers.fold(1) { acc, num -> acc * num }
        println(product) // 输出: 24
    
        val words = listOf("Kotlin", "is", "awesome")
        // 直接拼接
        val concat = words.fold("") { acc, word -> "$acc$word"}
        println(concat) // 输出: Kotlinisawesome
        // 添加分隔符
        val concatWithSpace = words.fold("") { acc, word -> if (acc.isEmpty()) word else "$acc $word"}
        println(concatWithSpace) // 输出: Kotlin is awesome
    }

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

  • 实例化函数类型

    Kotlin 使用函数类型(例如(Int) -> String)来声明处理函数:val onClick: () -> Unit = ...

    这些类型具有与函数签名(其参数和返回值)相对应的特殊符号:

    • 所有函数类型都有一个括号内的参数类型列表和一个返回类型:表示代表接受两个类型和的参数并返回类型值的函数的(A, B) -> C类型。参数类型列表可以为空,如。返回类型不能省略。A B C () -> A Unit
    • 函数类型可以选择性地具有附加的接收者类型,该类型在符号中的点之前指定:该类型表示可以在带有参数的A.(B) -> C接收者对象上调用并返回值的函数。带有接收者的函数文字通常与这些类型一起使用。A B C
    • 暂停函数属于一种特殊的函数类型,其符号中带有暂停修饰符,例如suspend () -> Unitsuspend A.(B) -> C

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

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

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

    我们有很多种方法可以获得函数类型的实例:

    • 在函数文字中使用代码块,采用以下形式之一:

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

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

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

      • 顶级函数、本地函数、成员函数或扩展函数:::isOdd,,String::toInt
      • 顶级属性、成员属性或扩展属性:List<Int>::size
      • 构造函数:::Regex

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

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

      kotlin 复制代码
      // 自定义函数类型
      typealias EventHandler = (String) -> Boolean
      
      // 自定义类实现函数类型 (String) -> Boolean
      class LoggingEventHandler : EventHandler {
          // 实现 invoke 方法,定义处理逻辑
          override fun invoke(event: String): Boolean {
              println("处理事件: $event")
              return event.isNotEmpty()
          }
      }
      fun main() {
          // 创建自定义类的实例
          val handler = LoggingEventHandler()
      
          // 直接像调用函数一样使用
          val result1 = handler("用户登录") // 输出: 处理事件: 用户登录
          println("结果: $result1")        // 输出: 结果: true
      
          val result2 = handler("")       // 输出: 处理事件:
          println("结果: $result2")        // 输出: 结果: false
      }
  • 调用函数类型实例

    invoke(...)f.invoke(x)或者仅仅来调用函数类型的值f(x)

    如果值具有接收者类型,则应将接收者对象作为第一个参数传递。调用具有接收者的函数类型值的另一种方法是将接收者对象添加到其前面,就好像该值是扩展函数一样:1.foo(2)

    kotlin 复制代码
    fun main() {
        val stringPlus: (String, String) -> String = String::plus
        val intPlus: Int.(Int) -> Int = Int::plus
    
        println(stringPlus.invoke("<-", "->")) // <--> 
        println(stringPlus("Hello, ", "world!")) // Hello, world! 
    
        println(intPlus.invoke(1, 1)) // 2
        println(intPlus(1, 2)) // 3
        println(2.intPlus(3)) // 5
    
    }
  • Lambda表达式语法

    Lambda 表达式的完整语法形式如下:

    kotlin 复制代码
    val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
    • Lambda 表达式总是被花括号 {} 包围着
    • 完整语法形式的参数声明位于花括号内,并具有可选的类型注释
    • 主体后跟随一个 ->
    • 如果 Lambda 的推断返回类型不是 Unit , 则 lambda 主体内的最后一个(活可能是单个)表达式将被视为返回值。

    如果省略所有可选注释,剩下的内容如下所示:

    kotlin 复制代码
    val sum = { x: Int, y: Int -> x + y }
  • 传递尾随 lambda

    按照 Kotlin 的约定,如果函数的最后一个参数是函数,那么作为相应参数传递的 lambda 表达式可以放在括号外面:

    kotlin 复制代码
    fun sum(a: Int, b: Int, result: (a:Int, b: Int) -> Int): Int {
        return result(a,b)
    }
    fun main() {
        val sum = sum(1, 2) { a, b -> a + b }
        println(sum) // 输出: 3
    }

    这种语法也称为尾随 lambda

    如果 lambda 是该调用中的唯一参数,则可以完全省略括号:

    kotlin 复制代码
    fun sum(result: (a:Int, b: Int) -> Int): Int {
        return result(1, 2)
    }
    fun main() {
        println(sum { a, b ->
            a + b
        }) // 输出: 3
    }
  • it: 单个参数的隐式名称

    Lambda 表达式只有一个参数是很常见的

    如果编译器可以解析没有任何参数的签名,则无需声明该参数 ->可以省略。该参数将以名称隐式声明 it:

    kotlin 复制代码
    fun main() {
        val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        // 过滤偶数(隐式参数 `it` 代表每个元素)
        val evenNumbers = numbers.filter { it % 2 == 0 }
        println(evenNumbers) // 输出 [2, 4, 6, 8, 10]
        // 将每个元素平方(`it` 代表元素)
        val squares = numbers.map { it * it }
        println(squares) // 输出 [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    }
  • 从 lambda 表达式返回值

    我们可以使用限定返回语法从 lambda 显式返回一个值。否则,将隐式返回最后一个表达式的值。

    kotlin 复制代码
    fun calculate(operation: (Int, Int) -> Int): Int {
        return operation(2, 3) // 调用 Lambda 并获取返回值
    }
    
    fun main() {
        val result = calculate { a, b ->
            return@calculate a + b // 显示返回 Lambda 的结果
        }
    
        println(result) // 输出: 5
    
        val sum: (Int, Int) -> Int = { a, b ->
            a + b  // 隐式返回结果
        }
    
        println(sum(2, 3)) // 输出: 5
    }
  • Lambda 中未使用的变量用下划线表示

    kotlin 复制代码
    fun main() {
        val map = mapOf(1 to "A", 2 to "B")
    
        // 忽略 Key,只使用 Value
        map.forEach { (_, value) -> print(value) } // 输出: AB
        println()
        // 忽略 Value,只使用 Key
        map.forEach { (key, _) -> print(key) } // 输出: 12
    }
  • 匿名函数

    上面的 lambda 表达式语法缺少一件事------指定函数返回类型的能力。在大多数情况下,这是不必要的,因为返回类型可以自动推断。但是,如果确实需要明确指定它,可以使用另一种语法:匿名函数。基本语法如下:

    kotlin 复制代码
    fun main() {
        val sum = fun(a: Int, b: Int): Int {
            return a + b
        }
        // 等价于
        val sum2 = fun(a: Int, b: Int): Int = a + b
    
        println(sum(2, 3)) // 输出: 5
        println(sum2(2, 3)) // 输出: 5
    }
    • 用匿名函数:
      • 需要显式 return 或复杂逻辑时。
      • 避免 Lambda 的"非局部返回"问题。
    • 用 Lambda:
      • 简单操作或作为函数最后一个参数时。
  • 带接收器的函数字面量

    在 Kotlin 中, 带有接收者的函数字面值(Function Literals with Receiver) 是一种特殊的 Lambda 或匿名函数,它允许在函数体内直接访问一个隐式的接收者对象(this)。这种特性广泛应用于 DSL(领域特定语言)构建、扩展函数和高阶函数中, 基本概念如下:

    • 接收者(Receiver):一个对象实例,作为函数执行的上下文。
    • 函数字面值:Lambda 表达式或匿名函数。
    • 关键语法 :在函数类型前添加 接收者类型.,例如 String.() -> Unit

    带接受者的Lambda

    kotlin 复制代码
    val greet: String.() -> Unit = { // String.() 表示这个 Lambda的接受者是 String 类型
        println("Hello, $this") // `this` 指向接收者 String
    }
    
    fun main() {
        "Kotlin".greet() // 输出: Hello, Kotlin
    }

    带接受者的匿名函数

    kotlin 复制代码
    val sum: Int.(Int) -> Int = fun Int.(other: Int): Int {
        return this + other // `this` 指向接收者 Int
    }
    
    fun main() {
        println(5.sum(3)) // 输出: 8
    }

    直接访问接收者成员 在带有接收者的 Lambda 中,可以像拓展函数一样访问接收者的属性和方法:

    kotlin 复制代码
    val buildString: StringBuilder.() -> Unit = {
        append("Hello") // 等价于 this.append()
        append(", Kotlin")
    }
    
    fun main() {
        val sb = StringBuilder()
        sb.buildString()
        println(sb.toString()) // 输出: Hello, Kotlin
    }

3.内联函数

在 Kotlin 中,内联函数(Inline Functions) 是一种通过编译器优化来减少高阶函数运行时开销的机制,主要解决 Lambda 表达式带来的性能问题(如函数调用开销和对象分配)

  • 内敛函数的核心作用

    • 消除 Lambda 的运行时开销:将 Lambda 的代码直接 "内敛" 到调用处, 避免创建匿名类对象
    • 支持非局部返回:允许 Lambda 中的 return 直接返回外层函数
    • 类型参数具体化(Reified Generics):在运行时访问泛型类型信息(通常被 JVM 擦除)
  • 基本语法

    使用 inline 关键字标记函数:

    kotlin 复制代码
    inline fun inlineFunc() {
        println("This is inlineFunc")
    }
    
    fun main() {
        inlineFunc() // 输出: This is inlineFunc
        println("This is inlineFunc") // 输出: This is inlineFunc
        
        // 调用 inlineFunc() 相当于直接将该内联函数内容直接展开一样
    }

    上述代码看似原理很简单,但是只知道这个是远远不够的,如果参数是 Lambda 表达式呢?声明的内联函数该如何展开呢?

    kotlin 复制代码
    inline fun inlineFunc(a: () -> Unit) {
        a()
        println("This is inlineFunc")
    }
    
    fun main() {
        inlineFunc { println("This is Lambda") }
        // 输出:
        // This is Lambda
        // This is inlineFunc
    }

    我们有两种展开方式可以实现上述输出结果,那么是那两种的?那种才是上述内联函数的本来展开方式呢?

    第一种展开方法是和上述一样,将 Lambda 表达式的内容直接展开,将 Lambda 表达式的内容看成是一串代码

    kotlin 复制代码
    inline fun inlineFunc(a: () -> Unit) {
        a()
        println("This is inlineFunc")
    }
    fun main() { // 第一种展开方式
        println("This is Lambda") // 输出: This is Lambda
        println("This is inlineFunc") // 输出: This is inlineFunc
    }

    第二种展开方式是将 Lambda 看成是一个匿名函数对象

    kotlin 复制代码
    inline fun inlineFunc(a: () -> Unit) {
        a()
        println("This is inlineFunc")
    }
    fun main() { // 第二种展开方式
        // 生成一个函数,返回值为Unit,直接invoke执行
        object : Function0<Unit> {
            override fun invoke() {
                println("This is Lambda") // 输出: This is Lambda
            }
        }.invoke()
        println("This is inlineFunc") // 输出: This is inlineFunc
    }

    我们知道,在 Koltin 中实现 Lambda 表达式是生成了一个函数对象,如果在循环中调用的话,就会频繁的创建对象,非常占用性能,inline 关键字就是为了解决频繁创建函数对象而带来的开销,所以 Kotlin 规定 inline 内联函数默认把所有 Lambda 参数都到对应位置展开,就是上述中第一种展开方式。

  • 非局部返回(Non-local Return)

    内联 Lambda 中的 return 可以退出外层函数:

    kotlin 复制代码
    inline fun runIfPositive(n: Int, action: () -> Unit) {
        if (n > 0) action()
    }
    fun test() {
        runIfPositive(5) {
            println("OK") 
            return // 直接返回 test() 函数
        }
        println("Not reached") // 不会执行
    }
    fun main() {
        test() // 输出: OK
    }
  • 类型参数具体化(Reified Generics)

    通过 reified 在运行时保留泛型类型:

    kotlin 复制代码
    inline fun <reified T> checkType(value: Any) {
        if (value is T) { // 直接检查类型(通常因类型擦除无法实现)
            println("Is ${T::class.simpleName}")
        }
    }
    
    fun main() {
        checkType<String>("Text") // 输出: Is String
    }
  • 控制内联范围

    • noinline : 禁止特定 Lambda 参数内联,和 inline 关键字不同之处在于,oninline 关键字是给 Lambda 表达式的参数标记的,前面说过,inline 标记函数,编译器会默认将标记函数中的 Lambda 参数到对应位置直接展开,但是有时候,我们希望 Lambda 参数不内联怎么办?当被 oninline 标记的参数会默认不内联,也就是说把它完整的函数调用保存下来

      kotlin 复制代码
      inline fun inlineFunc(a: () -> Unit, noinline b:() -> Unit) {
          a()
          b()
          println("This is inlineFunc")
      }
      
      fun main() {
          inlineFunc(
              { println("This is inline Lambda")},
              { println("This is noinline Lambda")}
          )
          // 输出:
          // This is inline Lambda
          // This is noinline Lambda 
          // This is inlineFunc 
      }

      等价于

      kotlin 复制代码
      fun main() {
          println(&quot;This is inline Lambda&quot;) // 输出: This is inline Lambda
          object : Function0&lt;Unit&gt; {
              override fun invoke() {
                  println(&quot;This is noinline Lambda&quot;) // 输出: This is noinline Lambda
              }
          }.invoke()
          println(&quot;This is inlineFunc&quot;) // 输出: This is inlineFunc
      }

      当我们需要 将 Lambda 存储为变量或传递给非内联函数时,可以使用 oninline 阻止内联:

      kotlin 复制代码
      fun main() {
          // 示例 1: 存储 Lambda 到变量
          val storedLambda = performOperation(
              inlined = { println("内联 Lambda 执行") }, // 内联
              noInlined = { println("非内联 Lambda 执行") } // 不内联,可存储
          )
          storedLambda() // 调用存储的 Lambda
      
      }
      
      // 内联函数,其中一个 Lambda 被标记为 noinline
      inline fun performOperation(
          inlined: () -> Unit,
          noinline noInlined: () -> Unit // 禁止内联
      ): () -> Unit {
      //    return inlined() // 内联到调用处 因为没有标记为 noinline ,所以属于内联参数,编译时直接展开 不是一个函数对象,无法返回,
          inlined()
          return noInlined // 返回 Lambda 对象(需禁止内联)
      }
    • crossinline : 禁止非局部返回,但保持内联

      在异步回调中使用 Lambda 时, 防止 return 意外终止外层函数

      kotlin 复制代码
      fun main() {
          // 示例 1: 直接调用(允许局部返回)
          runCrossinline {
              println("Crossinline Lambda 执行")
              return@runCrossinline // 局部返回 ✅
              // return // 编译错误:禁止非局部返回 ❌
          }
      
          // 示例 2: 在异步回调中使用
          postDelayed(1000) {
              println("延迟 1 秒后执行")
              // return // 编译错误:禁止直接返回 main()
          }
      }
      
      // 内联函数,Lambda 被标记为 crossinline
      inline fun runCrossinline(crossinline block: () -> Unit) {
          println("开始执行...")
          block()
          println("执行结束")
      }
      
      // 模拟异步回调
      inline fun postDelayed(
          delayMillis: Long,
          crossinline block: () -> Unit // 禁止非局部返回
      ) {
          Thread {
              Thread.sleep(delayMillis)
              block() // 在子线程调用,不允许直接返回 main()
          }.start()
      }

4.运算符重载

Kotlin 允许您为类型上的预定义运算符集提供自定义实现。这些运算符具有预定义的符号表示(如+或*)和优先级。要实现运算符,请为相应类型提供具有特定名称的成员函数或扩展函数。此类型将成为二元运算的左侧类型和一元运算的参数类型。

在 Kotlin 中,运算符重载是通过定义特定名称的成员函数或拓展函数来实现的,这些函数需要用 operator 关键字标记

  • 算法运算符

    kotlin 复制代码
    data class NormalPoint(val x: Int, val y: Int) {
        fun plus(other: Point): Point {
            return Point(x + other.x, y + other.y)
        }
    }
    data class Point(val x: Int, val y: Int) {
        // 重载 + 运算符
        operator fun plus(other: Point): Point {
            return Point(x + other.x, y + other.y)
        }
        // 重载 - 运算符
        operator fun minus(other: Point): Point {
            return Point(x - other.x, y - other.y)
        }
        // 重载 * 运算符
        operator fun times(factor: Int): Point {
            return Point(x * factor, y * factor)
        }
        // 重载 / 运算符
        operator fun div(divisor: Int): Point {
            return Point(x / divisor, y / divisor)
        }
        // 重载 % 运算符
        operator fun rem(modulus: Int): Point {
            return Point(x % modulus, y % modulus)
        }
    }
    
    fun main() {
        val n1 = NormalPoint(10, 20)
        val n2 = NormalPoint(5, 10)
    
        println("加法: ${n1 + n2}")  // 编译器报错 "NormalPoint"中的"plus"上需要"operator"修饰符.
        
        val p1 = Point(10, 20)
        val p2 = Point(5, 10)
    
        println("加法: ${p1 + p2}")  // 输出: Point(x=15, y=30)
        println("减法: ${p1 - p2}")  // 输出: Point(x=5, y=10)
        println("乘法: ${p1 * 3}")    // 输出: Point(x=30, y=60)
        println("除法: ${p1 / 2}")    // 输出: Point(x=5, y=10)
        println("取模: ${p1 % 3}")    // 输出: Point(x=1, y=2)
    
        
    }
  • 比较运算符

    kotlin 复制代码
    data class Rational(val numerator: Int, val denominator: Int) : Comparable<Rational> {
        
        // 重载 compareTo 实现比较运算符
        override operator fun compareTo(other: Rational): Int {
            val left = numerator.toDouble() / denominator
            val right = other.numerator.toDouble() / other.denominator
            return left.compareTo(right)
        }
    
        // 重载 equals (自动由 data class 生成, 这里展示原理)
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is Rational) return false
    
            val thisValue = numerator.toDouble() / denominator
            val otherValue = other.numerator.toDouble() / other.denominator
    
            return thisValue == otherValue
        }
    
        // 重载 hashCode (自动由 data class 生成)
        override fun hashCode(): Int {
            return (numerator.toDouble() / denominator).hashCode()
        }
    }
    
    fun main() {
        val r1 = Rational(1, 2)
        val r2 = Rational(2, 4)
        val r3 = Rational(3, 4)
    
        println("r1 == r2: ${r1 == r2}")  // 输出: r1 == r2: true
        println("r1 != r3: ${r1 != r3}")  // 输出: r1 != r3: true
        println("r1 < r3: ${r1 < r3}")    // 输出: r1 < r3: true
        println("r3 > r1: ${r3 > r1}")    // 输出: r3 > r1: true
        println("r1 >= r2: ${r1 >= r2}")  // 输出: r1 >= r2: true
        println("r1 <= r3: ${r1 <= r3}")  // 输出: r1 <= r3: true
    }
  • 复合赋值运算符

    kotlin 复制代码
    data class Vector(var x: Double, var y: Double) {
        // 重载 += 运算符
        operator fun plusAssign(other: Vector) {
            x += other.x
            y += other.y
        }
        // 重载 -= 运算符
        operator fun minusAssign(other: Vector) {
            x -= other.x
            y -= other.y
        }
        // 重载 *= 运算符
        operator fun timesAssign(factor: Double)  {
            x *= factor
            y *= factor
        }
    }
    
    fun main() {
        val v1 = Vector(1.5, 2.5)
        val v2 = Vector(0.5, 1.0)
    
        v1 += v2
        println("+= 操作后: $v1")  // 输出: += 操作后: Vector(x=2.0, y=3.5)
    
        v1 -= v2
        println("-= 操作后: $v1")  // 输出: -= 操作后: Vector(x=1.5, y=2.5)
    
        v1 *= 2.0
        println("*= 操作后: $v1")  // 输出: *= 操作后: Vector(x=3.0, y=5.0)
    }
  • 一元运算符

    kotlin 复制代码
    data class Complex(val real: Double, val imaginary: Double) {
        // 重载 + (正号) 运算符
        operator fun unaryPlus(): Complex {
            return this
        }
    
        // 重载 - (负号) 运算符
        operator fun unaryMinus(): Complex {
            return Complex(-real, -imaginary)
        }
    
        // 重载 ! (非) 运算符
        operator fun not(): Complex {
            return Complex(real, -imaginary)  // 共轭复数
        }
    
        // 重载 ++ 运算符
        operator fun inc(): Complex {
            return Complex(real + 1, imaginary + 1)
        }
    
        // 重载 -- 运算符
        operator fun dec(): Complex {
            return Complex(real - 1, imaginary - 1)
        }
    }
    
    fun main() {
        val c1 = Complex(3.0, 4.0)
    
        println("正号: ${+c1}")  // 输出: 正号: Complex(real=3.0, imaginary=4.0)
        println("负号: ${-c1}")  // 输出: 负号: Complex(real=-3.0, imaginary=-4.0)
        println("非运算: ${!c1}") // 输出: 非运算: Complex(real=3.0, imaginary=-4.0)
    
        var c2 = Complex(1.0, 2.0)
        println("原值: $c2")      // 输出: 原值: Complex(real=1.0, imaginary=2.0)
        println("++操作: ${++c2}") // 输出: ++操作: Complex(real=2.0, imaginary=3.0)
        println("--操作: ${--c2}") // 输出: --操作: Complex(real=1.0, imaginary=2.0)
    }
  • 索引访问运算符

    kotlin 复制代码
    class Matrix(val rows: Int, val cols: Int) {
        private val data = Array(rows) { DoubleArray(cols) }
    
        // 重载 get 运算符
        operator fun get(row: Int, col: Int): Double {
            return data[row][col]
        }
    
        // 重载 set 运算符
        operator fun set(row: Int, col: Int, value: Double) {
            data[row][col] = value
        }
    
        // 重载 invoke 运算符 (使对象可调用)
        operator fun invoke(block: Matrix.() -> Unit): Matrix {
            this.block()
            return this
        }
    }
    
    fun main() {
        val m = Matrix(2, 2)
    
        // 使用 set 运算符
        m[0, 0] = 1.0
        m[0, 1] = 2.0
        m[1, 0] = 3.0
        m[1, 1] = 4.0
    
        // 使用 get 运算符
        println("m[0,0] = ${m[0, 0]}")  // 输出: m[0,0] = 1.0
        println("m[1,1] = ${m[1, 1]}")  // 输出: m[1,1] = 4.0
    
        // 使用 invoke 运算符
        m {
            this[0, 0] = 5.0
            this[1, 1] = 6.0
        }
    
        println("修改后 m[0,0] = ${m[0, 0]}")  // 输出: 修改后 m[0,0] = 5.0
        println("修改后 m[1,1] = ${m[1, 1]}")  // 输出: 修改后 m[1,1] = 6.0
    }
  • 范围运算符

    kotlin 复制代码
    data class Date(val year: Int, val month: Int, val day: Int) : Comparable<Date> {
        // 实现 Comparable 接口
        override fun compareTo(other: Date): Int {
            return when {
                year != other.year -> year - other.year
                month != other.month -> month - other.month
                else -> day - other.day
            }
        }
    
        // 重载 rangeTo 运算符 (创建日期范围)
        operator fun rangeTo(other: Date): DateRange {
            return DateRange(this, other)
        }
    
        override fun toString(): String = "$year-$month-$day"
    }
    
    class DateRange(override val start: Date, override val endInclusive: Date) : ClosedRange<Date>, Iterable<Date> {
        // 实现迭代器
        override fun iterator(): Iterator<Date> {
            return object : Iterator<Date> {
                var current = start
    
                override fun hasNext(): Boolean = current <= endInclusive
    
                override fun next(): Date {
                    if (!hasNext()) throw NoSuchElementException()
                    val result = current
                    current = nextDay(current)
                    return result
                }
    
                private fun nextDay(date: Date): Date {
                    val (y, m, d) = date
                    return when {
                        d < daysInMonth(m, y) -> Date(y, m, d + 1)
                        m < 12 -> Date(y, m + 1, 1)
                        else -> Date(y + 1, 1, 1)
                    }
                }
    
                private fun daysInMonth(month: Int, year: Int): Int {
                    return when (month) {
                        2 -> if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) 29 else 28
                        4, 6, 9, 11 -> 30
                        else -> 31
                    }
                }
            }
        }
    }
    
    fun main() {
        val startDate = Date(2025, 3, 1)
        val endDate = Date(2025, 3, 5)
    
        // 使用 .. 运算符创建范围
        val dateRange = startDate..endDate
    
        // 遍历日期范围
        println("2025年3月1日到5日的日期:")
        for (date in dateRange) {
            println(date)
        }
        /* 输出:
        2023-1-1
        2023-1-2
        2023-1-3
        2023-1-4
        2023-1-5
        */
    
        // 使用 in 运算符检查日期是否在范围内
        val checkDate = Date(2025, 3, 3)
        println("$checkDate 是否在范围内: ${checkDate in dateRange}")  // 输出: true
    }

简介

在上一节,我们对Kotlin中面向对象编程(OOP)的相关知识有了大致的了解,本章节我们将去进一步了解函数、lambada表达式、内联函数、操作符重载、作用域函数。

目录

  1. 函数
  2. 高阶函数和Lambda
  3. 内联函数
  4. 运算符重载

1.函数

  • 函数的使用

    Kotlin 函数使用关键字 fun 声明:

    kotlin 复制代码
    fun double(x: Int): Int {
        return 2 * x
    }
    fun main() {
        val result = double(2)
        println("result: $result") // 输出为 result: 4
    
        println("result: ${double(3)}") // 输出为 result: 6
    }
    • fun : 定义函数的关键字
    • double : 函数名
    • x: Int : 定义的整型参数,参数名 x 在前,参数类型 Int 在后
    • Int : 定义函数的返回类型为 Int
    • { return 2 * x } : 函数体,double 函数的具体内容
    • 使用标准方法 val result = double(2) 调用了 double 函数
    • 函数有返回值,直接调用函数返回 result: ${double(3)}
  • 参数 函数参数使用 Pascal 符号定义 -名称:类型。参数使用逗号分隔,并且每个参数必须明确输入:

    kotlin 复制代码
    fun powerOf(number: Double, exponent: Int): Double {
        return number.pow(exponent)
    }
    
    fun main() {
        println(powerOf(3.0,3)) // 输出为 27.0
    }
  • 默认参数 函数参数可以有默认值,当跳过相应的参数时会使用这些默认值。这减少了重载的次数:

    kot 复制代码
    fun powerOf(number: Double, exponent: Int = 3): Double {
        return number.pow(exponent)
    }
    
    fun main() {
        println(powerOf(2.0)) // 输出为 8.0
    }

    使用 = 给参数设置默认值

    重写方法始终使用基方法的默认参数值。重写具有默认参数值的方法时,必须从签名中省略默认参数值:

    kotlin 复制代码
    open class Parent {
        open fun greet(name: String = "Guest") {
            println("Hello, $name!")
        }
    }
    
    class Child : Parent() {
        override fun greet(name: String = "Guest") { // 编译器报错 不允许重写函数为其参数指定默认值
            println("Hi, $name!")
        }
    }
    
    fun main() {
        val parent: Parent = Child()
        parent.greet() // 输出: Hello, Guest!
    }

    如果默认参数位于没有默认值的参数之前,则只能通过调用带有命名参数的函数来使用默认值:

    kotlin 复制代码
    class Parent {
        fun greet(name: String = "Guest", age: Int) {
            println("Hello, $name! you age is $age")
        }
    }
    fun main() {
        val parent = Parent()
        parent.greet(age = 22) // Hello, Guest! you age is 22
    }

    如果默认参数后的最后一个参数是lambda,则可以将其作为命名参数传递,也可以在括号外传递:

    kotlin 复制代码
    class Parent {
        fun greet(name: String = "Guest", age: Int, out: (name: String, age: Int) -> Unit) {
            out(name, age)
        }
    }
    
    fun main() {
        val parent = Parent()
        parent.greet(age = 22, out = {name, age ->
            println("Hello $name, you age is $age") // 输出为 Hello Guest, you age is 22
        })
        parent.greet(age = 22) { name, age ->
            println("Hello $name, you age is $age") // 输出为 Hello Guest, you age is 22
        }
    }
  • 命名参数

    调用函数时,我们可以命名一个或多个函数参数。当函数具有许多参数且难以将值与参数关联时(尤其是当参数为布尔值或null值时) ,这会很有用。

    当在函数调用中使用命名参数时,我们可以自由更改它们列出的顺序。如果想使用它们的默认值,可以完全省略这些参数。

    kotlin 复制代码
    class Parent {
        fun greet(name: String, age: Int = 22, isMarry: Boolean = false) {
            println("Hello $name, you age is $age, isMarry is $isMarry")
        }
    }
    fun main() {
        val parent = Parent()
        // 跳过所有默认值
        parent.greet("Lucy") // 输出为 Hello Lucy, you age is 22, isMarry is false
        // 还可以跳过具有默认值的特定参数,而不是省略所有参数。但是,在第一个跳过的参数之后,必须命名所有后续参数:
        parent.greet("June", isMarry = true) // 输出为 Hello June, you age is 22, isMarry is true
    }
  • 返回单位的函数

    如果函数没有返回有用的值,则其返回类型为UnitUnit是只有一个值的类型 - Unit。此值不必明确返回:

    kotlin 复制代码
    fun printHello(name: String?): Unit { // : Unit 可以省略
        if (name != null)
            println("Hello $name")
        else
            println("Hi there!")
    }
    fun main() {
        printHello(null) // 输出 Hi there!
    }
  • 单表达式函数

    当函数体由单个表达式组成时,可以省略花括号并在=符号后指定函数体, 当编译器可以推断出返回类型时,明确声明返回类型是可选的: :

    kotlin 复制代码
    fun double(x: Int): Int = x * 2
    fun double2(x: Int) = x * 2
    fun main() {
        println(double(3)) // 输出为 6
        println(double2(3)) // 输出为 6
    }
  • 可变数量的参数

    在 Kotlin 中,可变参数(Varargs) 允许函数接受任意数量的同一类型的参数。这是通过 vararg 关键字实现的。可变参数在需要处理不确定数量的输入时非常有用,例如处理一组数字、字符串或其他类型的数据

    kotlin 复制代码
    fun printNumbers(vararg numbers: Int) {
        for (number in numbers) {
            print(number)
        }
    }
    fun <T> asList(vararg ts: T): List<T> {
        val result = ArrayList<T>()
        for (t in ts) // ts is an Array
            result.add(t)
        return result
    }
    fun main() {
        printNumbers(1, 2, 3) // 输出: 1 2 3
        println()
        printNumbers(4, 5, 6, 7, 8) // 输出: 4 5 6 7 8
        println()
        println(asList(9, 10, 11)) // 输出: [9, 10, 11]
    }

    在函数内部,vararg类型的 -参数T可视为 的数组T,如上例所示,其中ts变量的类型为Array。

    只有一个参数可以标记为vararg。如果vararg参数不是列表中的最后一个,则可以使用命名参数语法传递后续参数的值,或者,如果参数具有函数类型,则通过在括号外传递 lambda。

    调用函数时vararg,可以单独传递参数,例如arrayOf(3, 4, 5, 6, 7, 8)。如果已经有一个数组并希望将其内容传递给函数,请使用展开运算符(在数组前加上*):

    kotlin 复制代码
    fun <T> asList(vararg ts: T): List<T> {
        val result = ArrayList<T>()
        for (t in ts) // ts is an Array
            result.add(t)
        return result
    }
    
    fun main() {
        val a = arrayOf(3, 4, 5, 6, 7, 8)
        println(asList(1, 2, 3, *a, 9, 10, 11)) // 输出: [1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    }
  • 中缀表达式

    标有关键字的函数infix也可以使用中缀表示法(省略调用时的点和括号)来调用。中缀函数必须满足以下要求:

    • 它们必须是成员函数或者扩展函数。

    • 它们必须有一个参数。

    • 该参数不能接受可变数量的参数,并且不能有默认值。

    kotlin 复制代码
    class Person(val name: String) {
        infix fun sayHelloTo(other: Person) {
            println("$name says hello to ${other.name}")
        }
    }
    fun main() {
        val alice = Person("Alice")
        val bob = Person("Bob")
        alice sayHelloTo bob // 输出: Alice says hello to Bob
    }
  • 本地函数

    在 Kotlin 中,本地函数(Local Functions) 是指定义在另一个函数内部的函数。这种函数的作用域仅限于其外部函数,无法在其他地方调用。本地函数可以访问外部函数的变量和参数:

    kotlin 复制代码
    fun main() {
        val name = "NPC:"
    
        fun printMessage(message: String) {
            println("$name$message")
        }
        printMessage("Welcome!") // 输出 NPC:Welcome!
    }

    本地函数可以修改外部变量:

    kotlin 复制代码
    fun main() {
        var count = 0
    
        fun increment() {
            count++
            println("Count: $count")
        }
    
        increment() // 输出 Count: 1
        increment() // 输出 Count: 2
        println("Count: $count") // 输出 Count: 2
    }

    本地函数非常适用于封装重复逻辑或分解复杂操作:

    kotlin 复制代码
    fun main() {
        // 本地函数
        fun calculateTotal(price: Double, quantity: Int): Double {
            // 计算折扣
            fun getDiscount(): Double {
                return if (quantity > 5) price * 0.1 else 0.0
            }
    
            // 计算税费
            fun getTax(subtotal: Double): Double {
                return subtotal * 0.08
            }
    
            val discount = getDiscount()
            val subtotal = (price * quantity) - discount
            return subtotal + getTax(subtotal)
        }
    
        val total = calculateTotal(100.0, 6)
        println("总金额: $total") // 输出 总金额: 637.2
    }
  • 成员函数

    Kotlin 中的 成员函数(Member Functions) 是定义在类或对象内部的函数,属于类的一部分。它们用于描述类的行为或操作类的数据(属性),是面向对象编程(OOP)的核心组成部分

    基本语法:

    kotlin 复制代码
    class Person(val name: String) {
        // 成员函数
        fun greet() {
            println("Hello, my name is $name")
        }
    }
    
    fun main() {
        val person = Person("Alice")
        person.greet() // 输出: Hello, my name is Alice
    }
  • 访问类的属性

    成员函数可以直接访问类的属性和其他成员(包括私有成员):

    kotlin 复制代码
    class Car(val brand: String, private var speed: Int = 0) {
        fun accelerate(amount: Int) {
            speed += amount // 访问私有属性
            println("加速到: $speed km/h")
        }
        fun getSpeed(): Int {
            return speed
        }
    }
    
    fun main() {
        val car = Car("Tesla")
        car.accelerate(50) // 输出: 加速到 50 km/h
        println("当前速度: ${car.getSpeed()}") // 输出: 当前速度: 50
    }
  • 继承与重写

    如果类被标记为 open,其成员函数可以被继承和重写:

    kotlin 复制代码
    open class Animal {
        open fun makeSound() {
            println("动物发出声音")
        }
    }
    
    class Dog : Animal() {
        override fun makeSound() {
            println("汪汪汪!")
        }
    }
    
    fun main() {
        val dog = Dog()
        dog.makeSound() // 输出: 汪汪汪!
    }
  • 函数重载

    成员函数支持重载(相同函数名,不同参数列表):

    kotlin 复制代码
    class Calculator {
        fun add(a: Int, b: Int): Int = a + b
        fun add(a: Double, b: Double): Double = a + b
    }
    
    fun main() {
        val calc = Calculator()
        println(calc.add(3, 5))   // 输出: 8
        println(calc.add(3.5, 2.5)) // 输出: 6.0
    }
  • 泛型函数

    Kotlin 中的 泛型函数(Generic Functions) 允许你编写可以处理多种数据类型的代码,同时保持类型安全性。通过泛型,你可以避免重复编写针对不同类型但逻辑相同的函数

    基本语法

    泛型函数通过在函数名后使用尖括号 声明类型参数(T 是占位符,可替换为任意标识符)。类型参数 T 可以在函数的参数、返回类型或函数体内使用。

    kotlin 复制代码
    // 泛型函数示例: 交换两个元素的值
    fun <T> swap(a: T, b: T): Pair<T, T> {
        return Pair(b, a)
    }
    fun main() {
        val swapped = swap(10, 20) // 类型判断为 Int
        println(swapped) // 输出: (20, 10)
    
        val swappedStr = swap("A", "B") // 类型判断为 String
        println(swappedStr) // 输出: (B, A)
    }

    类型约束

    通过 where 子句或 : 指定泛型类型的约束(如必须是某个类的子类或实现某个接口)。

    kotlin 复制代码
    // 要求 T 必须实现 Comparable 接口
    fun <T: Comparable<T>> max(a: T, b: T): T {
        return if (a > b) a else b
    }
    fun main() {
        println(max(3, 5)) // 输出: 5
        println(max("X", "Y")) // 输出: Y
    }

    多类型参数

    可声明多个泛型类型参数:

    kotlin 复制代码
    fun &lt;K, V&gt; toMap(key: K, value: V): Map&lt;K, V&gt; {
        return mapOf(key to value)
    }
    
    fun main() {
        val map = toMap(1, &quot;Apple&quot;)
        println(map) // 输出: {1=Apple}
    }x
  • 型变(Variance)

    Kotlin 通过 out(协变)和 in(逆变)控制泛型类型的继承关系:

    协变(out):允许子类型替代父类型(适用于生产者)。

    kotlin 复制代码
    // 协变示例:返回泛型类型
    fun <T> copyData(source: List<out T>, destination: MutableList<T>) {
        destination.addAll(source)
    }
    fun main() {
        // 源列表(子类型元素)
        val intList: List<Int> = listOf(1, 2, 3)
    
        // 目标列表(父类型容器)
        val numberList: MutableList<Number> = mutableListOf(10.5, 20.7)
    
        // 将 Int 列表复制到 Number 列表中
        copyData(intList, numberList)
    
        println(numberList) // 输出: [10.5, 20.7, 1, 2, 3]
    }

    逆变(in):允许父类型替代子类型(适用于消费者)

    kotlin 复制代码
    // 逆变示例:消费泛型类型
    fun <T> fillList(destination: MutableList<in T>, value: T) {
        destination.add(value)
    }
    
    fun main() {
        // 目标列表(父类型容器)
        val anyList: MutableList<Any> = mutableListOf("Hello", 100)
    
        // 向 anyList 中添加 String 类型元素
        fillList(anyList, "Kotlin")
    
        println(anyList) // 输出: [Hello, 100, Kotlin]
    }
  • 尾递归函数

    Kotlin 中的 尾递归函数(Tail Recursive Functions) 是一种特殊的递归形式,通过编译器优化可以避免递归调用时的栈溢出问题

    尾递归:函数的最后一个操作是递归调用自身(即递归调用后没有其他计算)。

    优化原理:Kotlin 编译器会将尾递归转换为等效的循环(while 或 for),从而避免递归调用栈的累积。

    普通递归

    kotlin 复制代码
    fun factorial(n: Int): Int {
        if (n == 0) return 1
        return n * factorial(n - 1) // 递归调用后还有乘法操作,不是尾递归
    }
    
    fun main() {
        println(factorial(5))  // 输出: 120
        println(factorial(10000)) // 栈溢出错误!
    }

    尾递归

    kotlin 复制代码
    tailrec fun factorialTailRec(n: Int, acc: Int = 1): Int {
        if (n == 0) return acc
        return factorialTailRec(n - 1, acc * n) // 最后一个操作是递归调用
    }
    
    fun main() {
        println(factorialTailRec(5))     // 输出: 120
        println(factorialTailRec(10000)) // 正常计算,无栈溢出
    }

    应用场景:

    • 计算斐波那列数列

      kotlin 复制代码
      tailrec fun fibonacci(n: Int, a: Int = 0, b: Int = 1): Int {
          return when (n) {
              0 -> a
              1 -> b
              else -> fibonacci(n - 1, b, a + b)
          }
      }
      
      fun main() {
          println(fibonacci(10)) // 输出: 55
      }
    • 遍历链表

      kotlin 复制代码
      data class Node(val value: Int, val next: Node?)
      
      tailrec fun sumNodes(node: Node?, acc: Int = 0): Int {
          return if (node == null) acc
          else sumNodes(node.next, acc + node.value)
      }
      
      fun main() {
          val list = Node(1, Node(2, Node(3, null)))
          println(sumNodes(list)) // 输出: 6
      }

2.高阶函数和Lambda

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

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

  • 高阶函数

    高阶函数是一种将函数作为参数或返回函数的函数。

    高阶函数的一个很好的例子是集合的函数式编程习惯用法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
    }
    fun main() {
        val numbers = listOf(1, 2, 3, 4)
        // 求和
        val sum = numbers.fold(0){ acc, num -> acc + num}
        println(sum) // 输出: 10
        // 求积
        val product = numbers.fold(1) { acc, num -> acc * num }
        println(product) // 输出: 24
    
        val words = listOf("Kotlin", "is", "awesome")
        // 直接拼接
        val concat = words.fold("") { acc, word -> "$acc$word"}
        println(concat) // 输出: Kotlinisawesome
        // 添加分隔符
        val concatWithSpace = words.fold("") { acc, word -> if (acc.isEmpty()) word else "$acc $word"}
        println(concatWithSpace) // 输出: Kotlin is awesome
    }

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

  • 实例化函数类型

    Kotlin 使用函数类型(例如(Int) -> String)来声明处理函数:val onClick: () -> Unit = ...

    这些类型具有与函数签名(其参数和返回值)相对应的特殊符号:

    • 所有函数类型都有一个括号内的参数类型列表和一个返回类型:表示代表接受两个类型和的参数并返回类型值的函数的(A, B) -> C类型。参数类型列表可以为空,如。返回类型不能省略。A B C () -> A Unit
    • 函数类型可以选择性地具有附加的接收者类型,该类型在符号中的点之前指定:该类型表示可以在带有参数的A.(B) -> C接收者对象上调用并返回值的函数。带有接收者的函数文字通常与这些类型一起使用。A B C
    • 暂停函数属于一种特殊的函数类型,其符号中带有暂停修饰符,例如suspend () -> Unitsuspend A.(B) -> C

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

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

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

    我们有很多种方法可以获得函数类型的实例:

    • 在函数文字中使用代码块,采用以下形式之一:

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

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

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

      • 顶级函数、本地函数、成员函数或扩展函数:::isOdd,,String::toInt
      • 顶级属性、成员属性或扩展属性:List<Int>::size
      • 构造函数:::Regex

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

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

      kotlin 复制代码
      // 自定义函数类型
      typealias EventHandler = (String) -> Boolean
      
      // 自定义类实现函数类型 (String) -> Boolean
      class LoggingEventHandler : EventHandler {
          // 实现 invoke 方法,定义处理逻辑
          override fun invoke(event: String): Boolean {
              println("处理事件: $event")
              return event.isNotEmpty()
          }
      }
      fun main() {
          // 创建自定义类的实例
          val handler = LoggingEventHandler()
      
          // 直接像调用函数一样使用
          val result1 = handler("用户登录") // 输出: 处理事件: 用户登录
          println("结果: $result1")        // 输出: 结果: true
      
          val result2 = handler("")       // 输出: 处理事件:
          println("结果: $result2")        // 输出: 结果: false
      }
  • 调用函数类型实例

    invoke(...)f.invoke(x)或者仅仅来调用函数类型的值f(x)

    如果值具有接收者类型,则应将接收者对象作为第一个参数传递。调用具有接收者的函数类型值的另一种方法是将接收者对象添加到其前面,就好像该值是扩展函数一样:1.foo(2)

    kotlin 复制代码
    fun main() {
        val stringPlus: (String, String) -> String = String::plus
        val intPlus: Int.(Int) -> Int = Int::plus
    
        println(stringPlus.invoke("<-", "->")) // <--> 
        println(stringPlus("Hello, ", "world!")) // Hello, world! 
    
        println(intPlus.invoke(1, 1)) // 2
        println(intPlus(1, 2)) // 3
        println(2.intPlus(3)) // 5
    
    }
  • Lambda表达式语法

    Lambda 表达式的完整语法形式如下:

    kotlin 复制代码
    val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
    • Lambda 表达式总是被花括号 {} 包围着
    • 完整语法形式的参数声明位于花括号内,并具有可选的类型注释
    • 主体后跟随一个 ->
    • 如果 Lambda 的推断返回类型不是 Unit , 则 lambda 主体内的最后一个(活可能是单个)表达式将被视为返回值。

    如果省略所有可选注释,剩下的内容如下所示:

    kotlin 复制代码
    val sum = { x: Int, y: Int -> x + y }
  • 传递尾随 lambda

    按照 Kotlin 的约定,如果函数的最后一个参数是函数,那么作为相应参数传递的 lambda 表达式可以放在括号外面:

    kotlin 复制代码
    fun sum(a: Int, b: Int, result: (a:Int, b: Int) -> Int): Int {
        return result(a,b)
    }
    fun main() {
        val sum = sum(1, 2) { a, b -> a + b }
        println(sum) // 输出: 3
    }

    这种语法也称为尾随 lambda

    如果 lambda 是该调用中的唯一参数,则可以完全省略括号:

    kotlin 复制代码
    fun sum(result: (a:Int, b: Int) -> Int): Int {
        return result(1, 2)
    }
    fun main() {
        println(sum { a, b ->
            a + b
        }) // 输出: 3
    }
  • it: 单个参数的隐式名称

    Lambda 表达式只有一个参数是很常见的

    如果编译器可以解析没有任何参数的签名,则无需声明该参数 ->可以省略。该参数将以名称隐式声明 it:

    kotlin 复制代码
    fun main() {
        val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        // 过滤偶数(隐式参数 `it` 代表每个元素)
        val evenNumbers = numbers.filter { it % 2 == 0 }
        println(evenNumbers) // 输出 [2, 4, 6, 8, 10]
        // 将每个元素平方(`it` 代表元素)
        val squares = numbers.map { it * it }
        println(squares) // 输出 [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    }
  • 从 lambda 表达式返回值

    我们可以使用限定返回语法从 lambda 显式返回一个值。否则,将隐式返回最后一个表达式的值。

    kotlin 复制代码
    fun calculate(operation: (Int, Int) -> Int): Int {
        return operation(2, 3) // 调用 Lambda 并获取返回值
    }
    
    fun main() {
        val result = calculate { a, b ->
            return@calculate a + b // 显示返回 Lambda 的结果
        }
    
        println(result) // 输出: 5
    
        val sum: (Int, Int) -> Int = { a, b ->
            a + b  // 隐式返回结果
        }
    
        println(sum(2, 3)) // 输出: 5
    }
  • Lambda 中未使用的变量用下划线表示

    kotlin 复制代码
    fun main() {
        val map = mapOf(1 to "A", 2 to "B")
    
        // 忽略 Key,只使用 Value
        map.forEach { (_, value) -> print(value) } // 输出: AB
        println()
        // 忽略 Value,只使用 Key
        map.forEach { (key, _) -> print(key) } // 输出: 12
    }
  • 匿名函数

    上面的 lambda 表达式语法缺少一件事------指定函数返回类型的能力。在大多数情况下,这是不必要的,因为返回类型可以自动推断。但是,如果确实需要明确指定它,可以使用另一种语法:匿名函数。基本语法如下:

    kotlin 复制代码
    fun main() {
        val sum = fun(a: Int, b: Int): Int {
            return a + b
        }
        // 等价于
        val sum2 = fun(a: Int, b: Int): Int = a + b
    
        println(sum(2, 3)) // 输出: 5
        println(sum2(2, 3)) // 输出: 5
    }
    • 用匿名函数:
      • 需要显式 return 或复杂逻辑时。
      • 避免 Lambda 的"非局部返回"问题。
    • 用 Lambda:
      • 简单操作或作为函数最后一个参数时。
  • 带接收器的函数字面量

    在 Kotlin 中, 带有接收者的函数字面值(Function Literals with Receiver) 是一种特殊的 Lambda 或匿名函数,它允许在函数体内直接访问一个隐式的接收者对象(this)。这种特性广泛应用于 DSL(领域特定语言)构建、扩展函数和高阶函数中, 基本概念如下:

    • 接收者(Receiver):一个对象实例,作为函数执行的上下文。
    • 函数字面值:Lambda 表达式或匿名函数。
    • 关键语法 :在函数类型前添加 接收者类型.,例如 String.() -> Unit

    带接受者的Lambda

    kotlin 复制代码
    val greet: String.() -> Unit = { // String.() 表示这个 Lambda的接受者是 String 类型
        println("Hello, $this") // `this` 指向接收者 String
    }
    
    fun main() {
        "Kotlin".greet() // 输出: Hello, Kotlin
    }

    带接受者的匿名函数

    kotlin 复制代码
    val sum: Int.(Int) -> Int = fun Int.(other: Int): Int {
        return this + other // `this` 指向接收者 Int
    }
    
    fun main() {
        println(5.sum(3)) // 输出: 8
    }

    直接访问接收者成员 在带有接收者的 Lambda 中,可以像拓展函数一样访问接收者的属性和方法:

    kotlin 复制代码
    val buildString: StringBuilder.() -> Unit = {
        append("Hello") // 等价于 this.append()
        append(", Kotlin")
    }
    
    fun main() {
        val sb = StringBuilder()
        sb.buildString()
        println(sb.toString()) // 输出: Hello, Kotlin
    }

3.内联函数

在 Kotlin 中,内联函数(Inline Functions) 是一种通过编译器优化来减少高阶函数运行时开销的机制,主要解决 Lambda 表达式带来的性能问题(如函数调用开销和对象分配)

  • 内敛函数的核心作用

    • 消除 Lambda 的运行时开销:将 Lambda 的代码直接 "内敛" 到调用处, 避免创建匿名类对象
    • 支持非局部返回:允许 Lambda 中的 return 直接返回外层函数
    • 类型参数具体化(Reified Generics):在运行时访问泛型类型信息(通常被 JVM 擦除)
  • 基本语法

    使用 inline 关键字标记函数:

    kotlin 复制代码
    inline fun inlineFunc() {
        println("This is inlineFunc")
    }
    
    fun main() {
        inlineFunc() // 输出: This is inlineFunc
        println("This is inlineFunc") // 输出: This is inlineFunc
        
        // 调用 inlineFunc() 相当于直接将该内联函数内容直接展开一样
    }

    上述代码看似原理很简单,但是只知道这个是远远不够的,如果参数是 Lambda 表达式呢?声明的内联函数该如何展开呢?

    kotlin 复制代码
    inline fun inlineFunc(a: () -> Unit) {
        a()
        println("This is inlineFunc")
    }
    
    fun main() {
        inlineFunc { println("This is Lambda") }
        // 输出:
        // This is Lambda
        // This is inlineFunc
    }

    我们有两种展开方式可以实现上述输出结果,那么是那两种的?那种才是上述内联函数的本来展开方式呢?

    第一种展开方法是和上述一样,将 Lambda 表达式的内容直接展开,将 Lambda 表达式的内容看成是一串代码

    kotlin 复制代码
    inline fun inlineFunc(a: () -> Unit) {
        a()
        println("This is inlineFunc")
    }
    fun main() { // 第一种展开方式
        println("This is Lambda") // 输出: This is Lambda
        println("This is inlineFunc") // 输出: This is inlineFunc
    }

    第二种展开方式是将 Lambda 看成是一个匿名函数对象

    kotlin 复制代码
    inline fun inlineFunc(a: () -> Unit) {
        a()
        println("This is inlineFunc")
    }
    fun main() { // 第二种展开方式
        // 生成一个函数,返回值为Unit,直接invoke执行
        object : Function0<Unit> {
            override fun invoke() {
                println("This is Lambda") // 输出: This is Lambda
            }
        }.invoke()
        println("This is inlineFunc") // 输出: This is inlineFunc
    }

    我们知道,在 Koltin 中实现 Lambda 表达式是生成了一个函数对象,如果在循环中调用的话,就会频繁的创建对象,非常占用性能,inline 关键字就是为了解决频繁创建函数对象而带来的开销,所以 Kotlin 规定 inline 内联函数默认把所有 Lambda 参数都到对应位置展开,就是上述中第一种展开方式。

  • 非局部返回(Non-local Return)

    内联 Lambda 中的 return 可以退出外层函数:

    kotlin 复制代码
    inline fun runIfPositive(n: Int, action: () -> Unit) {
        if (n > 0) action()
    }
    fun test() {
        runIfPositive(5) {
            println("OK") 
            return // 直接返回 test() 函数
        }
        println("Not reached") // 不会执行
    }
    fun main() {
        test() // 输出: OK
    }
  • 类型参数具体化(Reified Generics)

    通过 reified 在运行时保留泛型类型:

    kotlin 复制代码
    inline fun <reified T> checkType(value: Any) {
        if (value is T) { // 直接检查类型(通常因类型擦除无法实现)
            println("Is ${T::class.simpleName}")
        }
    }
    
    fun main() {
        checkType<String>("Text") // 输出: Is String
    }
  • 控制内联范围

    • noinline : 禁止特定 Lambda 参数内联,和 inline 关键字不同之处在于,oninline 关键字是给 Lambda 表达式的参数标记的,前面说过,inline 标记函数,编译器会默认将标记函数中的 Lambda 参数到对应位置直接展开,但是有时候,我们希望 Lambda 参数不内联怎么办?当被 oninline 标记的参数会默认不内联,也就是说把它完整的函数调用保存下来

      kotlin 复制代码
      inline fun inlineFunc(a: () -> Unit, noinline b:() -> Unit) {
          a()
          b()
          println("This is inlineFunc")
      }
      
      fun main() {
          inlineFunc(
              { println("This is inline Lambda")},
              { println("This is noinline Lambda")}
          )
          // 输出:
          // This is inline Lambda
          // This is noinline Lambda 
          // This is inlineFunc 
      }

      等价于

      kotlin 复制代码
      fun main() {
          println(&quot;This is inline Lambda&quot;) // 输出: This is inline Lambda
          object : Function0&lt;Unit&gt; {
              override fun invoke() {
                  println(&quot;This is noinline Lambda&quot;) // 输出: This is noinline Lambda
              }
          }.invoke()
          println(&quot;This is inlineFunc&quot;) // 输出: This is inlineFunc
      }

      当我们需要 将 Lambda 存储为变量或传递给非内联函数时,可以使用 oninline 阻止内联:

      kotlin 复制代码
      fun main() {
          // 示例 1: 存储 Lambda 到变量
          val storedLambda = performOperation(
              inlined = { println("内联 Lambda 执行") }, // 内联
              noInlined = { println("非内联 Lambda 执行") } // 不内联,可存储
          )
          storedLambda() // 调用存储的 Lambda
      
      }
      
      // 内联函数,其中一个 Lambda 被标记为 noinline
      inline fun performOperation(
          inlined: () -> Unit,
          noinline noInlined: () -> Unit // 禁止内联
      ): () -> Unit {
      //    return inlined() // 内联到调用处 因为没有标记为 noinline ,所以属于内联参数,编译时直接展开 不是一个函数对象,无法返回,
          inlined()
          return noInlined // 返回 Lambda 对象(需禁止内联)
      }
    • crossinline : 禁止非局部返回,但保持内联

      在异步回调中使用 Lambda 时, 防止 return 意外终止外层函数

      kotlin 复制代码
      fun main() {
          // 示例 1: 直接调用(允许局部返回)
          runCrossinline {
              println("Crossinline Lambda 执行")
              return@runCrossinline // 局部返回 ✅
              // return // 编译错误:禁止非局部返回 ❌
          }
      
          // 示例 2: 在异步回调中使用
          postDelayed(1000) {
              println("延迟 1 秒后执行")
              // return // 编译错误:禁止直接返回 main()
          }
      }
      
      // 内联函数,Lambda 被标记为 crossinline
      inline fun runCrossinline(crossinline block: () -> Unit) {
          println("开始执行...")
          block()
          println("执行结束")
      }
      
      // 模拟异步回调
      inline fun postDelayed(
          delayMillis: Long,
          crossinline block: () -> Unit // 禁止非局部返回
      ) {
          Thread {
              Thread.sleep(delayMillis)
              block() // 在子线程调用,不允许直接返回 main()
          }.start()
      }

4.运算符重载

Kotlin 允许您为类型上的预定义运算符集提供自定义实现。这些运算符具有预定义的符号表示(如+或*)和优先级。要实现运算符,请为相应类型提供具有特定名称的成员函数或扩展函数。此类型将成为二元运算的左侧类型和一元运算的参数类型。

在 Kotlin 中,运算符重载是通过定义特定名称的成员函数或拓展函数来实现的,这些函数需要用 operator 关键字标记

  • 算法运算符

    kotlin 复制代码
    data class NormalPoint(val x: Int, val y: Int) {
        fun plus(other: Point): Point {
            return Point(x + other.x, y + other.y)
        }
    }
    data class Point(val x: Int, val y: Int) {
        // 重载 + 运算符
        operator fun plus(other: Point): Point {
            return Point(x + other.x, y + other.y)
        }
        // 重载 - 运算符
        operator fun minus(other: Point): Point {
            return Point(x - other.x, y - other.y)
        }
        // 重载 * 运算符
        operator fun times(factor: Int): Point {
            return Point(x * factor, y * factor)
        }
        // 重载 / 运算符
        operator fun div(divisor: Int): Point {
            return Point(x / divisor, y / divisor)
        }
        // 重载 % 运算符
        operator fun rem(modulus: Int): Point {
            return Point(x % modulus, y % modulus)
        }
    }
    
    fun main() {
        val n1 = NormalPoint(10, 20)
        val n2 = NormalPoint(5, 10)
    
        println("加法: ${n1 + n2}")  // 编译器报错 "NormalPoint"中的"plus"上需要"operator"修饰符.
        
        val p1 = Point(10, 20)
        val p2 = Point(5, 10)
    
        println("加法: ${p1 + p2}")  // 输出: Point(x=15, y=30)
        println("减法: ${p1 - p2}")  // 输出: Point(x=5, y=10)
        println("乘法: ${p1 * 3}")    // 输出: Point(x=30, y=60)
        println("除法: ${p1 / 2}")    // 输出: Point(x=5, y=10)
        println("取模: ${p1 % 3}")    // 输出: Point(x=1, y=2)
    
        
    }
  • 比较运算符

    kotlin 复制代码
    data class Rational(val numerator: Int, val denominator: Int) : Comparable<Rational> {
        
        // 重载 compareTo 实现比较运算符
        override operator fun compareTo(other: Rational): Int {
            val left = numerator.toDouble() / denominator
            val right = other.numerator.toDouble() / other.denominator
            return left.compareTo(right)
        }
    
        // 重载 equals (自动由 data class 生成, 这里展示原理)
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is Rational) return false
    
            val thisValue = numerator.toDouble() / denominator
            val otherValue = other.numerator.toDouble() / other.denominator
    
            return thisValue == otherValue
        }
    
        // 重载 hashCode (自动由 data class 生成)
        override fun hashCode(): Int {
            return (numerator.toDouble() / denominator).hashCode()
        }
    }
    
    fun main() {
        val r1 = Rational(1, 2)
        val r2 = Rational(2, 4)
        val r3 = Rational(3, 4)
    
        println("r1 == r2: ${r1 == r2}")  // 输出: r1 == r2: true
        println("r1 != r3: ${r1 != r3}")  // 输出: r1 != r3: true
        println("r1 < r3: ${r1 < r3}")    // 输出: r1 < r3: true
        println("r3 > r1: ${r3 > r1}")    // 输出: r3 > r1: true
        println("r1 >= r2: ${r1 >= r2}")  // 输出: r1 >= r2: true
        println("r1 <= r3: ${r1 <= r3}")  // 输出: r1 <= r3: true
    }
  • 复合赋值运算符

    kotlin 复制代码
    data class Vector(var x: Double, var y: Double) {
        // 重载 += 运算符
        operator fun plusAssign(other: Vector) {
            x += other.x
            y += other.y
        }
        // 重载 -= 运算符
        operator fun minusAssign(other: Vector) {
            x -= other.x
            y -= other.y
        }
        // 重载 *= 运算符
        operator fun timesAssign(factor: Double)  {
            x *= factor
            y *= factor
        }
    }
    
    fun main() {
        val v1 = Vector(1.5, 2.5)
        val v2 = Vector(0.5, 1.0)
    
        v1 += v2
        println("+= 操作后: $v1")  // 输出: += 操作后: Vector(x=2.0, y=3.5)
    
        v1 -= v2
        println("-= 操作后: $v1")  // 输出: -= 操作后: Vector(x=1.5, y=2.5)
    
        v1 *= 2.0
        println("*= 操作后: $v1")  // 输出: *= 操作后: Vector(x=3.0, y=5.0)
    }
  • 一元运算符

    kotlin 复制代码
    data class Complex(val real: Double, val imaginary: Double) {
        // 重载 + (正号) 运算符
        operator fun unaryPlus(): Complex {
            return this
        }
    
        // 重载 - (负号) 运算符
        operator fun unaryMinus(): Complex {
            return Complex(-real, -imaginary)
        }
    
        // 重载 ! (非) 运算符
        operator fun not(): Complex {
            return Complex(real, -imaginary)  // 共轭复数
        }
    
        // 重载 ++ 运算符
        operator fun inc(): Complex {
            return Complex(real + 1, imaginary + 1)
        }
    
        // 重载 -- 运算符
        operator fun dec(): Complex {
            return Complex(real - 1, imaginary - 1)
        }
    }
    
    fun main() {
        val c1 = Complex(3.0, 4.0)
    
        println("正号: ${+c1}")  // 输出: 正号: Complex(real=3.0, imaginary=4.0)
        println("负号: ${-c1}")  // 输出: 负号: Complex(real=-3.0, imaginary=-4.0)
        println("非运算: ${!c1}") // 输出: 非运算: Complex(real=3.0, imaginary=-4.0)
    
        var c2 = Complex(1.0, 2.0)
        println("原值: $c2")      // 输出: 原值: Complex(real=1.0, imaginary=2.0)
        println("++操作: ${++c2}") // 输出: ++操作: Complex(real=2.0, imaginary=3.0)
        println("--操作: ${--c2}") // 输出: --操作: Complex(real=1.0, imaginary=2.0)
    }
  • 索引访问运算符

    kotlin 复制代码
    class Matrix(val rows: Int, val cols: Int) {
        private val data = Array(rows) { DoubleArray(cols) }
    
        // 重载 get 运算符
        operator fun get(row: Int, col: Int): Double {
            return data[row][col]
        }
    
        // 重载 set 运算符
        operator fun set(row: Int, col: Int, value: Double) {
            data[row][col] = value
        }
    
        // 重载 invoke 运算符 (使对象可调用)
        operator fun invoke(block: Matrix.() -> Unit): Matrix {
            this.block()
            return this
        }
    }
    
    fun main() {
        val m = Matrix(2, 2)
    
        // 使用 set 运算符
        m[0, 0] = 1.0
        m[0, 1] = 2.0
        m[1, 0] = 3.0
        m[1, 1] = 4.0
    
        // 使用 get 运算符
        println("m[0,0] = ${m[0, 0]}")  // 输出: m[0,0] = 1.0
        println("m[1,1] = ${m[1, 1]}")  // 输出: m[1,1] = 4.0
    
        // 使用 invoke 运算符
        m {
            this[0, 0] = 5.0
            this[1, 1] = 6.0
        }
    
        println("修改后 m[0,0] = ${m[0, 0]}")  // 输出: 修改后 m[0,0] = 5.0
        println("修改后 m[1,1] = ${m[1, 1]}")  // 输出: 修改后 m[1,1] = 6.0
    }
  • 范围运算符

    kotlin 复制代码
    data class Date(val year: Int, val month: Int, val day: Int) : Comparable<Date> {
        // 实现 Comparable 接口
        override fun compareTo(other: Date): Int {
            return when {
                year != other.year -> year - other.year
                month != other.month -> month - other.month
                else -> day - other.day
            }
        }
    
        // 重载 rangeTo 运算符 (创建日期范围)
        operator fun rangeTo(other: Date): DateRange {
            return DateRange(this, other)
        }
    
        override fun toString(): String = "$year-$month-$day"
    }
    
    class DateRange(override val start: Date, override val endInclusive: Date) : ClosedRange<Date>, Iterable<Date> {
        // 实现迭代器
        override fun iterator(): Iterator<Date> {
            return object : Iterator<Date> {
                var current = start
    
                override fun hasNext(): Boolean = current <= endInclusive
    
                override fun next(): Date {
                    if (!hasNext()) throw NoSuchElementException()
                    val result = current
                    current = nextDay(current)
                    return result
                }
    
                private fun nextDay(date: Date): Date {
                    val (y, m, d) = date
                    return when {
                        d < daysInMonth(m, y) -> Date(y, m, d + 1)
                        m < 12 -> Date(y, m + 1, 1)
                        else -> Date(y + 1, 1, 1)
                    }
                }
    
                private fun daysInMonth(month: Int, year: Int): Int {
                    return when (month) {
                        2 -> if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) 29 else 28
                        4, 6, 9, 11 -> 30
                        else -> 31
                    }
                }
            }
        }
    }
    
    fun main() {
        val startDate = Date(2025, 3, 1)
        val endDate = Date(2025, 3, 5)
    
        // 使用 .. 运算符创建范围
        val dateRange = startDate..endDate
    
        // 遍历日期范围
        println("2025年3月1日到5日的日期:")
        for (date in dateRange) {
            println(date)
        }
        /* 输出:
        2023-1-1
        2023-1-2
        2023-1-3
        2023-1-4
        2023-1-5
        */
    
        // 使用 in 运算符检查日期是否在范围内
        val checkDate = Date(2025, 3, 3)
        println("$checkDate 是否在范围内: ${checkDate in dateRange}")  // 输出: true
    }
相关推荐
鸿蒙布道师14 分钟前
鸿蒙NEXT开发Base64工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
jiet_h1 小时前
Android adb 的功能和用法
android·adb
美狐美颜sdk2 小时前
美颜SDK兼容性挑战:如何让美颜滤镜API适配iOS与安卓?
android·深度学习·ios·美颜sdk·第三方美颜sdk·视频美颜sdk
居然是阿宋2 小时前
深入理解 YUV 颜色空间:从原理到 Android 视频渲染
android·音视频
KevinWang_3 小时前
DialogFragment 不适合复用
android
古鸽100863 小时前
Audio Hal 介绍
android
小叶不焦虑4 小时前
关于 Android 系统回收站的实现
android
木西4 小时前
从0到1搭建一个RN应用从开发测试到上架全流程
android·前端·react native
小橙子20775 小时前
一条命令配置移动端(Android / iOS)自动化环境
android·ios·自动化
和煦的春风5 小时前
案例分析 | SurfaceFlinger Binder RT 被降级到CFS
android