2Kotlin in Action

Kotlin基础

基础语法

变量函数类枚举when表达式

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第2章:Kotlin基础(Kotlin Basics)

一、导读

1.1 本章学习目标

第2章深入讲解 Kotlin 的基础语法和核心概念。如果说第1章是"鸟瞰全景",那么第2章就是"脚踏实地"。你将学习 Kotlin 的基本构建块——变量、函数、类、表达式和控制流,并理解它们与 Java 的根本区别。

本章的核心学习目标包括:

  • 掌握 Kotlin 的基本语法元素:函数、变量、类、枚举和 when 表达式
  • 理解 Kotlin 的枚举和 when 表达式的强大组合:它们比 Java 的 switch 强大得多
  • 学会使用智能转换(Smart Casts)简化类型检查代码
  • 掌握 for 循环和 while 循环的惯用写法
  • 理解范围(Range)和区间(Progression)的概念
  • 掌握异常处理的基本语法

1.2 为什么本章如此重要

基础语法看似简单,但 Kotlin 的基础语法中蕴含着许多与 Java 截然不同的设计理念。比如 when 表达式不仅仅是 switch 的替代品——它是一个模式匹配工具,可以与智能转换、范围检查、类型检查等结合使用,表达力远超 Java 的 switch。

再比如 Kotlin 的枚举类——它不仅仅是常量的集合,而是一个真正的类,可以包含属性和方法。这些"小"特性的组合,使得 Kotlin 在日常编码中的表达力远超 Java。

掌握本章内容,你就能用 Kotlin 写出地道的(idiomatic)代码,而不是"用 Kotlin 语法写的 Java"。

1.3 核心问题引导

Kotlin 的 when 表达式为什么比 Java 的 switch 更强大?它能匹配什么?

什么是智能转换?它如何减少显式类型转换的需要?

Kotlin 的 for 循环与 Java 的 for 循环有什么本质区别?

什么是范围(Range)?它如何与 in 操作符配合使用?

Kotlin 的异常处理与 Java 有什么不同?为什么 Kotlin 没有受检异常?


二、核心概念详解

2.1 函数

2.1.1 基本函数声明

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

kotlin
fun sum(a: Int, b: Int): Int {
    return a + b
}

// 单表达式函数:当函数体只有一个表达式时,可以省略花括号和 return
fun sumShort(a: Int, b: Int): Int = a + b

// 如果返回 Unit(类似 Java 的 void),可以省略返回类型
fun printSum(a: Int, b: Int) {
    println("Sum: ${a + b}")
}

2.1.2 参数默认值与命名参数

Kotlin 支持参数默认值,这大大减少了函数重载的需要:

kotlin
fun createAlert(
    message: String,
    title: String = "Alert",
    dismissible: Boolean = true
) {
    // ...
}

// 调用方式
createAlert("Something happened")
createAlert("Error occurred", "Error")
createAlert("Warning", dismissible = false)    // 命名参数
createAlert(message = "Info", title = "Info", dismissible = true)

在 Java 中,你需要创建三个重载方法来达到同样的效果。命名参数让调用代码更加清晰——参数的含义一目了然。

2.1.3 vararg 参数

kotlin
fun printAll(vararg messages: String) {
    for (message in messages) {
        println(message)
    }
}

printAll("Hello", "World", "Kotlin")

// 使用 * 操作符展开数组
val words = arrayOf("Hello", "World")
printAll(*words)    // 展开操作符(spread operator)

2.2 变量与类型

2.2.1 字符串模板

Kotlin 的字符串模板是日常编码中使用频率极高的特性:

kotlin
val name = "Kotlin"
val version = 1.7

// 简单模板
val greeting = "Hello, $name!"

// 表达式模板
val info = "$name version ${version + 0.1}"

// 多行字符串(原始字符串)
val json = """
    {
        "name": "$name",
        "version": $version
    }
""".trimIndent()

原始字符串(raw string)使用三个双引号包裹,内部不需要转义字符,非常适合 JSON、SQL、正则表达式等场景。trimIndent() 会自动去除公共的前导空白。

2.2.2 枚举类

Kotlin 的枚举类比 Java 的强大得多——它是真正的类,可以包含属性和方法:

kotlin
enum class Color(
    val r: Int, val g: Int, val b: Int
) {
    RED(255, 0, 0),
    GREEN(0, 255, 0),
    BLUE(0, 0, 255);
    
    fun rgb(): Int = (r * 256 + g) * 256 + b
}

println(Color.BLUE.rgb())    // 输出: 255
println(Color.BLUE.r)        // 输出: 0

枚举类还可以定义抽象方法,让每个枚举常量提供自己的实现:

kotlin
enum class PlanetType {
    TERRESTRIAL {
        override fun description(): String = "Rocky planet"
    },
    GAS_GIANT {
        override fun description(): String = "Gas giant planet"
    };
    
    abstract fun description(): String
}

2.2.3 when 表达式

when 是 Kotlin 中最灵活的分支控制结构,它是 switch 的超级进化版:

kotlin
fun describeNumber(number: Int): String = when (number) {
    0 -> "Zero"
    1 -> "One"
    2, 3 -> "Two or three"
    in 4..10 -> "Between four and ten"
    in 11..100 -> "Between eleven and one hundred"
    else -> "Something else"
}

// 不带参数的 when(替代 if-else 链)
fun describeTemperature(temp: Int): String = when {
    temp < 0 -> "Freezing"
    temp in 0..15 -> "Cold"
    temp in 16..25 -> "Comfortable"
    temp in 26..35 -> "Hot"
    else -> "Extremely hot"
}

// 与类型检查结合
fun describeAny(any: Any): String = when (any) {
    is Int -> "Integer: $any"
    is String -> "String of length ${any.length}"    // 智能转换!
    is Boolean -> "Boolean: $any"
    is Long -> "Long integer"
    else -> "Unknown type"
}

when 是一个表达式,它有返回值。这意味着你可以直接将 when 的结果赋给变量或作为函数返回值。这是与 Java switch 的根本区别之一。

2.3 智能转换(Smart Casts)

Kotlin 编译器能够追踪 is 检查的结果,并在后续代码中自动进行类型转换:

kotlin
fun processValue(value: Any): String {
    if (value !is String) return "Not a string"
    // 这里 value 已经是 String 类型了
    return value.uppercase()
}

// 与 && 和 || 结合
fun safeLength(value: Any?): Int {
    if (value != null && value is String) {
        return value.length    // 双重智能转换:非 null + String
    }
    return 0
}

// 在 when 中
fun formatValue(value: Any): String = when (value) {
    is Int -> value + 1                    // Int
    is String -> "${value}!"               // String
    is Boolean -> if (value) "yes" else "no"    // Boolean
    else -> value.toString()               // Any
}

智能转换的前提是编译器能确保值在检查和使用之间没有被修改。对于 val 局部变量,这总是成立的。对于属性,只有在没有自定义 getter 且是线程安全的情况下才会触发智能转换。

2.4 循环与迭代

2.4.1 for 循环

Kotlin 的 for 循环基于迭代器,类似于 Java 的增强 for 循环:

kotlin
val fruits = listOf("Apple", "Banana", "Cherry")

// 基本遍历
for (fruit in fruits) {
    println(fruit)
}

// 带索引遍历
for ((index, fruit) in fruits.withIndex()) {
    println("$index: $fruit")
}

// 遍历 Map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
for ((key, value) in map) {
    println("$key -> $value")
}

2.4.2 范围与区间

Kotlin 的范围(Range)是一个非常有用的概念:

kotlin
// 创建范围
val range = 1..10         // 1 到 10(包含 10)
val until = 1 until 10    // 1 到 9(不包含 10)
val downTo = 10 downTo 1  // 10 到 1(递减)

// 步长
val step = 1..10 step 2   // 1, 3, 5, 7, 9

// 字符范围
val letters = 'a'..'z'
for (c in 'A'..'Z') print(c)

// 范围操作
val inRange = 5 in 1..10       // true
val notInRange = 15 in 1..10   // false

// 实用场景
for (i in 1..5) print(i)       // 12345
for (i in 5 downTo 1) print(i) // 54321
for (i in 0..10 step 2) print(i) // 0246810

2.4.3 while 循环

Kotlin 的 while 和 do-while 循环与 Java 基本相同,但有一个有趣的区别——Kotlin 没有 break 标签和 continue 标签,但可以通过带标签的循环实现类似功能:

kotlin
outer@ for (i in 1..100) {
    for (j in 1..100) {
        if (i * j > 50) break@outer
        println("$i * $j = ${i * j}")
    }
}

2.5 异常处理

2.5.1 基本语法

Kotlin 的异常处理语法与 Java 类似,但有一个关键区别:Kotlin 没有受检异常(checked exceptions)。

kotlin
fun readFile(path: String): String {
    try {
        return File(path).readText()
    } catch (e: FileNotFoundException) {
        println("File not found: $path")
        return ""
    } catch (e: IOException) {
        println("IO error: ${e.message}")
        throw e
    } finally {
        println("Cleanup")
    }
}

2.5.2 为什么没有受检异常

Java 的受检异常要求你在方法签名中声明可能抛出的异常,或者用 try-catch 捕获。这在实践中导致了几个问题:

  • 异常声明污染:一个底层异常会迫使所有上层方法都声明它
  • 空洞的 catch 块:开发者经常写空的 catch 块或 throw new RuntimeException(e) 来绕过
  • 破坏封装:内部实现的异常细节泄漏到了 API 签名中

Kotlin 的设计者认为受检异常的代价大于收益,因此所有异常在 Kotlin 中都是非受检的。你可以选择捕获任何异常,但编译器不会强制你这样做。

2.5.3 try 作为表达式

在 Kotlin 中,try 是一个表达式,它有返回值:

kotlin
val number: Int = try {
    "42".toInt()
} catch (e: NumberFormatException) {
    0    // 解析失败时的默认值
}

2.6 Nothing 类型

Kotlin 有一个特殊的类型 Nothing,它表示"永远不会返回值":

kotlin
fun fail(message: String): Nothing {
    throw IllegalStateException(message)
}

// Nothing 在类型系统中是任何类型的子类型
val x: Int = if (condition) {
    computeValue()
} else {
    fail("Condition must be true")    // 返回 Nothing,但赋值给 Int 是合法的
}

Nothing 类型在与 ?: 操作符结合时特别有用:

kotlin
val company: Company = employee.company ?: fail("No company")
// 编译器知道 fail() 返回 Nothing,所以 company 在这里一定是非 null 的

三、实战案例

3.1 案例一:用 when 实现一个状态机

kotlin
enum class OrderState {
    NEW, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}

data class Order(val id: String, val state: OrderState)

fun transitionTo(order: Order, newState: OrderState): Order {
    val canTransition = when (order.state) {
        OrderState.NEW -> newState in setOf(
            OrderState.CONFIRMED, OrderState.CANCELLED
        )
        OrderState.CONFIRMED -> newState in setOf(
            OrderState.SHIPPED, OrderState.CANCELLED
        )
        OrderState.SHIPPED -> newState == OrderState.DELIVERED
        OrderState.DELIVERED, OrderState.CANCELLED -> false
    }
    
    return if (canTransition) {
        println("Order ${order.id}: ${order.state} -> $newState")
        Order(order.id, newState)
    } else {
        println("Invalid transition: ${order.state} -> $newState")
        order
    }
}

fun stateDescription(state: OrderState): String = when (state) {
    OrderState.NEW -> "Order placed, awaiting confirmation"
    OrderState.CONFIRMED -> "Order confirmed, preparing to ship"
    OrderState.SHIPPED -> "Order shipped, in transit"
    OrderState.DELIVERED -> "Order delivered successfully"
    OrderState.CANCELLED -> "Order cancelled"
}

fun main() {
    var order = Order("ORD-001", OrderState.NEW)
    println(stateDescription(order.state))
    
    order = transitionTo(order, OrderState.CONFIRMED)
    println(stateDescription(order.state))
    
    order = transitionTo(order, OrderState.SHIPPED)
    println(stateDescription(order.state))
    
    order = transitionTo(order, OrderState.DELIVERED)
    println(stateDescription(order.state))
}

3.2 案例二:使用范围和 when 实现成绩评定系统

kotlin
data class Grade(val score: Double, val studentName: String)

fun evaluateGrade(grade: Grade): String {
    val letterGrade = when (grade.score) {
        in 90.0..100.0 -> "A"
        in 80.0 until 90.0 -> "B"
        in 70.0 until 80.0 -> "C"
        in 60.0 until 70.0 -> "D"
        in 0.0 until 60.0 -> "F"
        else -> throw IllegalArgumentException("Invalid score: ${grade.score}")
    }
    
    return "${grade.studentName}: $letterGrade (${grade.score})"
}

fun processGrades(grades: List<Grade>) {
    val results = grades.map { evaluateGrade(it) }
    
    // 按等级分组
    val grouped = grades.groupBy { grade ->
        when (grade.score) {
            in 90.0..100.0 -> "Excellent"
            in 80.0 until 90.0 -> "Good"
            in 70.0 until 80.0 -> "Average"
            in 60.0 until 70.0 -> "Below Average"
            else -> "Failing"
        }
    }
    
    grouped.forEach { (category, students) ->
        println("$category (${students.size} students):")
        students.forEach { println("  - ${it.studentName}: ${it.score}") }
    }
}

3.3 案例三:安全的 JSON 解析

kotlin
data class Config(
    val host: String,
    val port: Int,
    val debug: Boolean = false
)

fun parseConfig(json: Map<String, Any?>): Config? {
    val host = json["host"] as? String ?: return null
    val port = (json["port"] as? Number)?.toInt() ?: return null
    val debug = json["debug"] as? Boolean ?: false
    
    // 验证端口范围
    if (port !in 1..65535) return null
    
    return Config(host, port, debug)
}

fun main() {
    val validJson = mapOf("host" to "localhost", "port" to 8080, "debug" to true)
    val config = parseConfig(validJson)
    
    when (config) {
        null -> println("Invalid configuration")
        else -> println("Server: ${config.host}:${config.port}, Debug: ${config.debug}")
    }
}

四、本章小结

第2章深入讲解了 Kotlin 的基础语法:

函数:支持默认参数、命名参数和 vararg,大幅减少了函数重载的需要。单表达式函数语法让简单函数的定义更加简洁。

when 表达式:Kotlin 最灵活的分支结构,可以匹配值、范围、类型、任意条件。它是一个表达式,有返回值。

智能转换:编译器自动追踪类型检查的结果,消除了大量显式类型转换的需要。

范围(Range):1..10、until、downTo、step 等语法让数值范围的操作变得直观。

异常处理:Kotlin 没有受检异常,所有异常都是非受检的。try 是一个表达式,可以有返回值。

Nothing 类型:表示"永不返回"的特殊类型,在与 ?: 结合时能帮助编译器进行类型推断。


五、思考与练习

思考题

Kotlin 的 when 表达式相比 Java 的 switch 语句有哪些优势?为什么 Kotlin 不需要 break?

智能转换在什么情况下会失效?为什么编译器对 var 属性的智能转换比 val 更谨慎?

Kotlin 取消受检异常是利大于弊还是弊大于利?在实际项目中,如何确保异常被正确处理?

Nothing 类型在类型系统中扮演什么角色?为什么它是所有类型的子类型?

编程练习

编写一个函数,接收一个 Any? 参数,使用 when 表达式返回其类型的描述字符串。处理 null、Int、String、List、Map 等类型。

用枚举类和 when 表达式实现一个简易的计算器,支持加减乘除四种运算。

编写一个函数,接收一个整数列表,使用范围和 filter 找出其中所有的偶数、奇数,以及超出 0-100 范围的数。

实现一个 safeDivide 函数,使用 try 表达式处理除零和非数字输入的情况。


2/8Kotl
0%