学习Kotlin语法(三)
简介
在上一节,我们对Kotlin中面向对象编程(OOP)的相关知识有了大致的了解,本章节我们将去进一步了解函数、lambada表达式、内联函数、操作符重载、作用域函数。
目录
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<Any> = 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 }
带有接收者的函数文字可以用作带有接收者的函数类型的值。
- lambda表达式:
使用对现有声明的可调用引用:
- 顶级函数、本地函数、成员函数或扩展函数:
::isOdd,,String::toInt - 顶级属性、成员属性或扩展属性:
List<Int>::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 表达式总是被花括号
传递尾随 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 关键字标记
算法运算符
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) }
比较运算符
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
}
复合赋值运算符
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)
}
一元运算符
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)
}
索引访问运算符
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
}
范围运算符
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语法(三)的更多相关文章
- ios -- 教你如何轻松学习Swift语法(三) 完结篇
前言:swift语法基础篇(二)来了,想学习swift的朋友可以拿去参考哦,有兴趣可以相互探讨,共同学习哦. 一.自动引用计数 1.自动引用计数工作机制 1.1 swift和o ...
- 浅谈Kotlin(三):类
浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 前言: 已经学习了前 ...
- 我的MYSQL学习心得(三) 查看字段长度
我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...
- 学习Python的三种境界
前言 王国维在<人间词话>中将读书分为了三种境界:"古今之成大事业.大学问者,必经过三种之境界:'昨夜西风凋碧树,独上高楼,望尽天涯路'.此第一境也.'衣带渐宽终不悔,为伊消得人 ...
- ios -- 教你如何轻松学习Swift语法(一)
目前随着公司开发模式的变更,swift也显得越发重要,相对来说,swift语言更加简洁,严谨.但对于我来说,感觉swift细节的处理很繁琐,可能是还没适应的缘故吧.基本每写一句代码,都要对变量的数据类 ...
- 程序员带你学习安卓开发,十天快速入-对比C#学习java语法
关注今日头条-做全栈攻城狮,学代码也要读书,爱全栈,更爱生活.提供程序员技术及生活指导干货. 如果你真想学习,请评论学过的每篇文章,记录学习的痕迹. 请把所有教程文章中所提及的代码,最少敲写三遍,达到 ...
- C++Primer第5版学习笔记(三)
C++Primer第5版学习笔记(三) 第四/五章的重难点内容 你可以点击这里回顾第三章内容 因为第五章的内容比较少,因此和第四章的笔记内容合并. 第四章是 ...
- WCF学习心得----(三)服务承载
WCF学习心得----(三)服务承载 这一章节花费了好长的时间才整理个大概,主要原因是初次接触这个东西,在做练习实践的过程中,遇到了很多的问题,有些问题到目前还没有得以解决.所以在这一章节中,有一个承 ...
- (转载)CSS3与页面布局学习总结(三)——BFC、定位、浮动、7种垂直居中方法
目录 一.BFC与IFC 1.1.BFC与IFC概要 1.2.如何产生BFC 1.3.BFC的作用与特点 二.定位 2.2.relative 2.3.absolute 2.4.fixed 2.5.z- ...
- 《DOM Scripting》学习笔记-——第三章 DOM
<Dom Scripting>学习笔记 第三章 DOM 本章内容: 1.节点的概念. 2.四个DOM方法:getElementById, getElementsByTagName, get ...
随机推荐
- Arcgis加载Geoserver矢量切片
原帖地址 洒家废物 - Arcgis加载Geoserver矢量切片 准备点线面图层并发布图层组 此处我准备了石家庄市的县界名称(点).高速公路(线).县界(面),依次发布geoserver服务,创建图 ...
- Excel函数公式大全(图文详解)
---------------------------- ----------------------------------------------------------------------- ...
- java基础知识回顾之java Socket学习
UDP传输:面向无连接的协议,不可靠,只是把应用程序传给IP层的数据报包发送出去,不保证发送出去的数据报包能到达目的地.不用再客户端和服务器端建立连接,没有超时重发等机制,传输速度快是它的优点.就像寄 ...
- Secure Face Matching Using Fully Homomorphic Encryption-2018:学习
本文学习论文"Secure Face Matching Using Fully Homomorphic Encryption-2018"和"基于全同态加密的人脸特征密文认 ...
- Delphi Cxgrid获取选中行列,排序规则,当前正在编辑的单元格内的值
本文转自以下网址,感谢作者分享 https://blog.csdn.net/pcent/article/details/8169112 cxGrid1DBTableView1.Controller.F ...
- GDAL矢量数据集相关接口的资源控制问题
1. 引言 笔者在<使用GDAL读写矢量文件>这篇文章中总结了通过GDAL读写矢量的具体实现.不过这篇文章中并没有谈到涉及到矢量数据集相关接口的资源控制问题.具体来说,GDAL/OGR诞生 ...
- RocketMQ实战—9.营销系统代码初版
大纲 1.基于条件和画像筛选用户的业务分析和实现 2.全量用户促销活动数据模型分析以及创建操作 3.Producer和Consumer的工程代码实现 4.基于抽象工厂模式的消息推送实现 5.全量用户促 ...
- C#进行word模板占位符替换的几种工具
word模板中,包含一些需要替换的项,比如{{姓名}} {{年龄}}或者$姓名$ $年龄$,从数据库获取信息后,对模板进行替换操作生成新的word文档. 简单对以下四种工具做了一下测试: 1.NPOI ...
- 零基础使用AI辅助编写易简历小程序的一些心得体会
春节期间利用了一点时间体验了Copilot开发了一个小程序,先说结论: AI只是AI,并不能取代程序员. 你能做的,AI能做的更快:你不能做的,AI就大概率会糊弄你. 开发小程序的背景就是本身有一个易 ...
- Gradle的安装及换源详细教程
Gradle是一个基于JVM的构建工具,用于自动化构建.测试和部署项目. 1. 安装Gradle a. 首先,确保你已经安装了Java Development Kit (JDK),并且已经配置了JAV ...