简介
在上一节,我们对Kotlin中面向对象编程(OOP)的相关知识有了大致的了解,本章节我们将去进一步了解函数、lambada表达式、内联函数、操作符重载、作用域函数。
目录
- 函数
- 函数的使用
- 参数
- 默认参数
- 命名参数
- 返回单位的函数
- 表达式函数
- 可变数量的参数
- 中缀表达式
- 本地函数
- 成员函数
- 访问类的属性
- 继承与重写
- 函数重载
- 泛型函数
- 型变
- 尾递归函数
- 高阶函数和Lambda
- 高阶函数
- 实例化函数类型
- 调用函数类型实例
- Lambda表达式语法
- 传递尾随 Lambda
- it: 单个参数的隐式名称
- 从 Lambda 表达式返回值
- Lamb 中未使用的变量用下划线表示
- 匿名函数
- 带接收器的函数字面量
- 内联函数
- 内联函数核心作用
- 内联函数的基本语法
- 非局部返回
- 类型参数具体化
- 控制内联范围
- 运算符重载
- 算法运算符
- 比较运算符
- 符合赋值运算符
- 一元运算符
- 索引访问运算符
- 范围运算符
1.函数
- 函数的使用
Kotlin 函数使用关键字 fun 声明:- 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: $
- 参数
函数参数使用 Pascal 符号定义 -名称:类型。参数使用逗号分隔,并且每个参数必须明确输入:- fun powerOf(number: Double, exponent: Int): Double {
- return number.pow(exponent)
- }
- fun main() {
- println(powerOf(3.0,3)) // 输出为 27.0
- }
复制代码 - 默认参数
函数参数可以有默认值,当跳过相应的参数时会使用这些默认值。这减少了重载的次数:- fun powerOf(number: Double, exponent: Int = 3): Double {
- return number.pow(exponent)
- }
- fun main() {
- println(powerOf(2.0)) // 输出为 8.0
- }
复制代码 使用 = 给参数设置默认值
重写方法始终使用基方法的默认参数值。重写具有默认参数值的方法时,必须从签名中省略默认参数值:- 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!
- }
复制代码 如果默认参数位于没有默认值的参数之前,则只能通过调用带有命名参数的函数来使用默认值:- 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,则可以将其作为命名参数传递,也可以在括号外传递:- 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值时) ,这会很有用。
当在函数调用中使用命名参数时,我们可以自由更改它们列出的顺序。如果想使用它们的默认值,可以完全省略这些参数。- 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
- }
复制代码 - 返回单位的函数
如果函数没有返回有用的值,则其返回类型为Unit。Unit是只有一个值的类型 - Unit。此值不必明确返回:- fun printHello(name: String?): Unit { // : Unit 可以省略
- if (name != null)
- println("Hello $name")
- else
- println("Hi there!")
- }
- fun main() {
- printHello(null) // 输出 Hi there!
- }
复制代码 - 单表达式函数
当函数体由单个表达式组成时,可以省略花括号并在=符号后指定函数体, 当编译器可以推断出返回类型时,明确声明返回类型是可选的: :- 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 关键字实现的。可变参数在需要处理不确定数量的输入时非常有用,例如处理一组数字、字符串或其他类型的数据- 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)。如果已经有一个数组并希望将其内容传递给函数,请使用展开运算符(在数组前加上*):- 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也可以使用中缀表示法(省略调用时的点和括号)来调用。中缀函数必须满足以下要求:
- 它们必须是成员函数或者扩展函数。
- 它们必须有一个参数。
- 该参数不能接受可变数量的参数,并且不能有默认值。
- 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) 是指定义在另一个函数内部的函数。这种函数的作用域仅限于其外部函数,无法在其他地方调用。本地函数可以访问外部函数的变量和参数:- fun main() {
- val name = "NPC:"
- fun printMessage(message: String) {
- println("$name$message")
- }
- printMessage("Welcome!") // 输出 NPC:Welcome!
- }
复制代码 本地函数可以修改外部变量:- fun main() {
- var count = 0
- fun increment() {
- count++
- println("Count: $count")
- }
- increment() // 输出 Count: 1
- increment() // 输出 Count: 2
- println("Count: $count") // 输出 Count: 2
- }
复制代码 本地函数非常适用于封装重复逻辑或分解复杂操作:- 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)的核心组成部分
基本语法:- 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
- }
复制代码 - 访问类的属性
成员函数可以直接访问类的属性和其他成员(包括私有成员):- 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,其成员函数可以被继承和重写:- open class Animal {
- open fun makeSound() {
- println("动物发出声音")
- }
- }
- class Dog : Animal() {
- override fun makeSound() {
- println("汪汪汪!")
- }
- }
- fun main() {
- val dog = Dog()
- dog.makeSound() // 输出: 汪汪汪!
- }
复制代码 - 函数重载
成员函数支持重载(相同函数名,不同参数列表):- 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 可以在函数的参数、返回类型或函数体内使用。- // 泛型函数示例: 交换两个元素的值
- 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 子句或 : 指定泛型类型的约束(如必须是某个类的子类或实现某个接口)。- // 要求 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
- }
复制代码 多类型参数
可声明多个泛型类型参数:- fun <K, V> toMap(key: K, value: V): Map<K, V> {
- return mapOf(key to value)
- }
- fun main() {
- val map = toMap(1, "Apple")
- println(map) // 输出: {1=Apple}
- }x
复制代码 - 型变(Variance)
Kotlin 通过 out(协变)和 in(逆变)控制泛型类型的继承关系:
协变(out):允许子类型替代父类型(适用于生产者)。- // 协变示例:返回泛型类型
- 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):允许父类型替代子类型(适用于消费者)- // 逆变示例:消费泛型类型
- fun <T> fillList(destination: MutableList<in T>, value: T) {
- destination.add(value)
- }
- fun main() {
- // 目标列表(父类型容器)
- val anyList: MutableList = mutableListOf("Hello", 100)
- // 向 anyList 中添加 String 类型元素
- fillList(anyList, "Kotlin")
- println(anyList) // 输出: [Hello, 100, Kotlin]
- }
复制代码 - 尾递归函数
Kotlin 中的 尾递归函数(Tail Recursive Functions) 是一种特殊的递归形式,通过编译器优化可以避免递归调用时的栈溢出问题
尾递归:函数的最后一个操作是递归调用自身(即递归调用后没有其他计算)。
优化原理:Kotlin 编译器会将尾递归转换为等效的循环(while 或 for),从而避免递归调用栈的累积。
普通递归- fun factorial(n: Int): Int {
- if (n == 0) return 1
- return n * factorial(n - 1) // 递归调用后还有乘法操作,不是尾递归
- }
- fun main() {
- println(factorial(5)) // 输出: 120
- println(factorial(10000)) // 栈溢出错误!
- }
复制代码 尾递归- 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)) // 正常计算,无栈溢出
- }
复制代码 应用场景:
- 计算斐波那列数列
- 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
- }
复制代码 - 遍历链表
- 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。它需要一个初始累加器值和一个组合函数,并通过将当前累加器值与每个收集元素连续组合来构建其返回值,每次替换累加器值:- 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 () -> Unit或suspend 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::size,
- 构造函数:::Regex
这些包括指向特定实例成员的绑定可以调用引用foo::toString:。
- 使用实现函数类型作为接口的自定义类的实例:
- // 自定义函数类型
- 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)。- 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 表达式的完整语法形式如下:- val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
复制代码
- Lambda 表达式总是被花括号 {} 包围着
- 完整语法形式的参数声明位于花括号内,并具有可选的类型注释
- 主体后跟随一个 ->
- 如果 Lambda 的推断返回类型不是 Unit , 则 lambda 主体内的最后一个(活可能是单个)表达式将被视为返回值。
如果省略所有可选注释,剩下的内容如下所示:- val sum = { x: Int, y: Int -> x + y }
复制代码 - 传递尾随 lambda
按照 Kotlin 的约定,如果函数的最后一个参数是函数,那么作为相应参数传递的 lambda 表达式可以放在括号外面:- 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 是该调用中的唯一参数,则可以完全省略括号:- 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:- 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 显式返回一个值。否则,将隐式返回最后一个表达式的值。- 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 中未使用的变量用下划线表示
- 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 表达式语法缺少一件事——指定函数返回类型的能力。在大多数情况下,这是不必要的,因为返回类型可以自动推断。但是,如果确实需要明确指定它,可以使用另一种语法:匿名函数。基本语法如下:- 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- val greet: String.() -> Unit = { // String.() 表示这个 Lambda的接受者是 String 类型
- println("Hello, $this") // `this` 指向接收者 String
- }
- fun main() {
- "Kotlin".greet() // 输出: Hello, Kotlin
- }
复制代码 带接受者的匿名函数- val sum: Int.(Int) -> Int = fun Int.(other: Int): Int {
- return this + other // `this` 指向接收者 Int
- }
- fun main() {
- println(5.sum(3)) // 输出: 8
- }
复制代码 直接访问接收者成员
在带有接收者的 Lambda 中,可以像拓展函数一样访问接收者的属性和方法:- 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 关键字标记函数:- inline fun inlineFunc() {
- println("This is inlineFunc")
- }
- fun main() {
- inlineFunc() // 输出: This is inlineFunc
- println("This is inlineFunc") // 输出: This is inlineFunc
-
- // 调用 inlineFunc() 相当于直接将该内联函数内容直接展开一样
- }
复制代码 上述代码看似原理很简单,但是只知道这个是远远不够的,如果参数是 Lambda 表达式呢?声明的内联函数该如何展开呢?- 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 表达式的内容看成是一串代码- 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 看成是一个匿名函数对象- 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 可以退出外层函数:- 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 在运行时保留泛型类型:- 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 标记的参数会默认不内联,也就是说把它完整的函数调用保存下来
- 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
- }
复制代码 等价于- fun main() {
- println("This is inline Lambda") // 输出: This is inline Lambda
- object : Function0<Unit> {
- override fun invoke() {
- println("This is noinline Lambda") // 输出: This is noinline Lambda
- }
- }.invoke()
- println("This is inlineFunc") // 输出: This is inlineFunc
- }
复制代码 当我们需要 将 Lambda 存储为变量或传递给非内联函数时,可以使用 oninline 阻止内联:- 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 意外终止外层函数- 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 关键字标记
<ul>算法运算符- 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)
-
- }
复制代码 比较运算符
[code]data class Rational(val numerator: Int, val denominator: Int) : Comparable { // 重载 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 |