第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 关键字声明函数:
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 支持参数默认值,这大大减少了函数重载的需要:
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 参数
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 的字符串模板是日常编码中使用频率极高的特性:
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 的强大得多——它是真正的类,可以包含属性和方法:
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枚举类还可以定义抽象方法,让每个枚举常量提供自己的实现:
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 的超级进化版:
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 检查的结果,并在后续代码中自动进行类型转换:
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 循环:
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)是一个非常有用的概念:
// 创建范围
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) // 02468102.4.3 while 循环
Kotlin 的 while 和 do-while 循环与 Java 基本相同,但有一个有趣的区别——Kotlin 没有 break 标签和 continue 标签,但可以通过带标签的循环实现类似功能:
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)。
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 是一个表达式,它有返回值:
val number: Int = try {
"42".toInt()
} catch (e: NumberFormatException) {
0 // 解析失败时的默认值
}2.6 Nothing 类型
Kotlin 有一个特殊的类型 Nothing,它表示"永远不会返回值":
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// Nothing 在类型系统中是任何类型的子类型
val x: Int = if (condition) {
computeValue()
} else {
fail("Condition must be true") // 返回 Nothing,但赋值给 Int 是合法的
}Nothing 类型在与 ?: 操作符结合时特别有用:
val company: Company = employee.company ?: fail("No company")
// 编译器知道 fail() 返回 Nothing,所以 company 在这里一定是非 null 的三、实战案例
3.1 案例一:用 when 实现一个状态机
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 实现成绩评定系统
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 解析
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 表达式处理除零和非数字输入的情况。