重学Kotlin(二)表达式与运算符
前言
在 Kotlin 中,几乎所有语句都是表达式。 也就是说,它们都有返回值。 这点跟 Java 不太一样(Java 中大多数语句没有返回值)。
举个例子:
val max = if (a > b) a else b
这里 if 不是语句(statement),而是一个表达式(expression)!
它会返回一个值,所以我们可以直接赋值给变量。
一、if 表达式
val result = if (x > 10) {
println("大于10")
x
} else {
println("小于等于10")
0
}
🔹 if 返回最后一行的值。
🔹 不需要三元运算符(? :),Kotlin 的 if 自带返回值功能。
二、when 表达式(类似 switch)
val day = 7
val result = when (day) {
1 -> "星期一"
2 -> "星期二"
3 -> "星期三"
4 -> "星期四"
5 -> "星期五"
in 6..7 -> "周末"
else -> "未知"
}
print(result) // 周末
val str = when (val today = array[3]) {
in 1..5 -> "星期${today}是工作日"
6, 7 -> "星期${today}周末"
else -> "未知"
}
print(str) // 星期3是工作日
🔹 when 也是表达式,有返回值。
🔹 可以匹配常量、区间、类型,甚至是条件表达式。
🔹 括号内能对变量赋值,但只能在表达式内部使用。
三、try 表达式
val num = try {
"123".toInt()
} catch (e: NumberFormatException) {
-1
}
🔹 try-catch 也能作为表达式使用。
🔹 它返回最后一行的值。
四、Lambda 表达式
1.什么是 Lambda 表达式?
Lambda 表达式就是“匿名函数”: 它没有名字,但能像函数一样被调用或传递。
举个最简单的例子:
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2)) // 输出 3
✅ 这里的 { x: Int, y: Int -> x + y }
就是一个 Lambda 表达式,相当于:
fun sum(x: Int, y: Int) = x + y
只是没有函数名。
2.Lambda 的类型与函数类型
Lambda 是一种 函数类型对象。 它的类型写法为:
(参数类型列表) -> 返回类型
例如:
val sum: (Int, Int) -> Int = { a, b -> a + b }
Kotlin 中所有函数(包括 lambda)都是一等公民,可以像变量一样:
- 存入变量;
- 当参数传递;
- 当返回值返回。
3.Lambda 的语法
通用格式如下:
{ 参数列表 -> 函数体 }
| 位置 | 作用 | 示例 |
|---|---|---|
| 参数列表 | 定义输入参数 | x: Int, y: Int |
-> | 分隔符 | 固定写法 |
| 函数体 | 返回最后一行的值 | x + y |
下面是一些 Lambda 表达式的简写或语法糖
(1)类型推断
如果上下文能推断出类型,可以省略参数类型:
val sum: (Int, Int) -> Int = { x, y -> x + y }
(2)默认参数名 it
当 lambda 只有一个参数时,可以省略参数名和箭头 ->,直接使用 it。
val double = { it * 2 }
println(double(3)) // 输出 6
(3)lambda 作为最后一个参数的语法糖
当一个函数的最后一个参数是函数类型时,可以把 lambda 写在括号外面:
list.forEach({ println(it) }) // 常规写法
list.forEach { println(it) } // 推荐写法(更简洁)
甚至可以省略括号(DSL 风格):
run {
println("Hello Lambda!")
}
(4)Lambda 直接作为表达式返回
如果函数体只有一行 Lambda 可以直接返回,不必写 return:
fun square(x: Int) = { x * x } // Lambda 返回表达式
注意: 带标签的 return(非局部返回)
return 在 lambda 中有两种行为:
- 普通函数返回:
return结束函数 - lambda 返回:要用标签或匿名函数
举例说明
❌ 错误写法(return 会结束外层函数)
fun test() {
listOf(1, 2, 3).forEach {
if (it == 2) return // 返回整个 test()
println(it)
}
println("done") // 永远不会执行
}
✅ 正确写法(使用标签)
fun test() {
listOf(1, 2, 3).forEach {
if (it == 2) return@forEach // 返回当前 lambda
println(it)
}
println("done")
}
输出:
1
3
done
(5)使用 _ 忽略参数
如果 Lambda 有多个参数,但某个参数不使用,可以用 _ 忽略(Kotlin 1.7+ 支持):
val list = listOf("a", "bb", "ccc")
list.forEachIndexed { index, _ -> println(index) }
五、Elvis 运算符表达式(?:)
val name: String? = null
val displayName = name ?: "Unknown"
🔹 如果 name 为 null,就返回右边的 "Unknown"。
🔹 常用于安全处理空值。
六、安全调用表达式(?.)
val length = name?.length ?: 0
🔹 如果 name 为 null,不会抛异常,而是直接返回 null。
🔹 再结合 Elvis 运算符可以提供默认值。
七、运算符总览
Kotlin 的运算符基本可以分为以下几类
| 分类 | 运算符 | Kotlin 说明 |
|---|---|---|
| 算术 | + - * / % | 基础运算 |
| 比较 | == != > < >= <= | 内容比较 |
| 引用比较 | === !== | 地址比较 |
| 逻辑 | && || ! | 逻辑短路 |
| 位运算 | shl, shr, and... | 用函数表示 |
| 区间 | .., downTo, step, in | 常用于 for 循环 |
| 空安全 | ?. ?: !! | 空指针安全 |
| 类型 | is, as, as? | 类型检查与转换 |
| 引用 | :: | 函数或属性引用 |
| 自定义 | operator fun plus() | 运算符重载 |
八、运算符重载(自定义运算符)
Kotlin 支持你自定义运算符的行为(只要语义合理)。
必须使用关键字 operator。
下方链接可以查看所有支持的运算符
Kotlin 官方文档 运算符重载
例如:
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
fun main() {
val p1 = Point(1, 2)
val p2 = Point(3, 4)
val p3 = p1 + p2 // 调用了 plus()
println(p3) // 输出 Point(x=4, y=6)
}
Kotlin 会把
p1 + p2编译成p1.plus(p2)。
九、中缀函数(infix)
中缀(infix)的意思就是“放在中间的”。 在 Kotlin 中,它允许你用更自然的语法调用某些函数。
例如:
val result = 3 plus 5
这里的 plus 就是一个中缀函数(infix function),
等价于普通调用:
val result = 3.plus(5)
也就是说:
a plus b≡a.plus(b)
十、如何定义中缀函数
一个函数想被中缀调用,必须满足以下三个条件:
- 是成员函数(成员方法)或扩展函数
- 只有一个参数
- 用
infix关键字声明
示例 1:定义中缀函数
class Calculator {
infix fun add(x: Int): Int {
return x + 10
}
}
fun main() {
val c = Calculator()
println(c add 5) // 中缀写法
println(c.add(5)) // 普通写法,效果一样
}
输出:
15
15
示例 2:扩展函数的中缀写法
infix fun Int.plusTen(x: Int): Int = this + x + 10
fun main() {
val result = 3 plusTen 5
println(result) // 输出 18
}
这里:
3是调用者 (this)5是参数 (x)
示例 3:结合集合操作
Kotlin 标准库中已经定义了不少中缀函数,比如:
| 中缀函数 | 作用 | 示例 |
|---|---|---|
to | 创建 Pair | "A" to "B" → Pair("A", "B") |
downTo | 创建递减区间 | 5 downTo 1 → [5,4,3,2,1] |
until | 创建不包含上限的区间 | 1 until 5 → [1,2,3,4] |
step | 设置步长 | 1..10 step 2 → [1,3,5,7,9] |
示例:
val map = mapOf("name" to "Tom", "age" to 18)
val range = 10 downTo 1 step 2
十一、优点与注意事项
优点:
- 可读性强,语义自然(像 DSL)
- 写法简洁,适合领域特定语言(DSL)设计
注意:
- 只能有一个参数
- 不能使用在多个参数的函数上
- 中缀函数的优先级较低,如果混合运算,建议加括号
val x = 3 add 5 multiply 2 // ❌ 不确定顺序
val y = (3 add 5) multiply 2 // ✅ 建议写法
十二、构建 DSL
class Person {
var name = ""
var age = 0
}
infix fun Person.named(n: String): Person {
this.name = n
return this
}
infix fun Person.withAge(a: Int): Person {
this.age = a
return this
}
fun main() {
val p = Person() named "Tom" withAge 20
println("${p.name}, ${p.age}")
}
输出:
Tom, 20