重学Kotlin(二)表达式与运算符

65 阅读6分钟

重学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"

🔹 如果 namenull,就返回右边的 "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 ba.plus(b)


十、如何定义中缀函数

一个函数想被中缀调用,必须满足以下三个条件

  1. 是成员函数(成员方法)或扩展函数
  2. 只有一个参数
  3. 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