2、Kotlin语言基础:变量、常量、基本数据类型、类型推断、字符串模板、条件表达式、循环、when表达式
Kotlin 作为 Jetpack Compose 官方推荐的开发语言,其简洁、安全、表达力强的特性在 UI 声明式编程中体现得淋漓尽致。无论是定义状态、处理用户交互,还是构建复杂的业务逻辑,变量、控制流等基础语法都是每一行 Compose 代码的基石。本章将深入剖析 Kotlin 语言的核心基础,帮助你建立扎实的语法功底,为后续学习 Composable 函数、状态管理以及高级 UI 组件打下坚实基础。我们将从变量与常量的声明规则出发,逐步掌握类型推断、字符串模板、条件与循环控制,并通过实战案例理解这些语法在 Compose 开发中的真实应用场景。
2.1 变量与常量:val 与 var 的正确选择
核心原理
Kotlin 中声明变量使用 var(可变)和 val(不可变)两个关键字。val 类似于 Java 中的 final,一旦赋值便不可修改;var 则允许重新赋值。在 Jetpack Compose 中,强烈推荐优先使用 val,因为不可变性能够显著降低 UI 状态管理的复杂度,避免因变量意外修改导致的界面不一致问题。Kotlin 编译器会强制检查 val 的不可变性,任何尝试重新赋值的操作都会导致编译错误。
val,仅在确有必要时才使用 var。这不仅是编码规范,更是函数式编程思想的体现——不可变数据天然线程安全,且更容易追踪状态变化。
类型推断与显式声明
Kotlin 具备强大的类型推断能力,编译器能够根据初始值自动推导变量类型。例如 val count = 10 会自动推断为 Int 类型。但某些场景下需要显式声明类型,比如变量初始值为 null 或希望使用父类型时。显式声明的语法为 val name: String = "Kotlin"。在 Compose 中,当声明状态变量时,类型推断与显式声明经常结合使用:
// 类型推断
val isVisible = true // Boolean
val title = "Hello" // String
val price = 19.99 // Double
// 显式类型声明
val count: Int = 100
val message: String? = null // 可空类型必须显式声明
实战场景:Compose 中的状态声明
在 Compose 中,我们经常使用 var 配合 remember 来声明可变状态,但对外暴露时通常使用 val 只读接口。这种模式既保证了内部可变性,又对外提供了不可变视图:
@Composable
fun Counter() {
// 内部使用 var 管理可变状态
var count by remember { mutableStateOf(0) }
// 对外展示使用 val 读取
val currentCount = count
Button(onClick = { count++ }) {
Text("点击次数: $currentCount")
}
}
常见误区与总结
- 误区一:认为
val等同于 Java 的final。实际上val只保证引用不可变,如果指向的是可变对象(如MutableList),对象内部仍可修改。 - 误区二:过度使用
var。在 Compose 中,过多的可变状态会导致重组频繁,影响性能。 - 误区三:忽略类型推断的边界。当表达式复杂或涉及泛型时,显式声明类型能提高代码可读性。
2.2 基本数据类型与类型系统
核心原理
Kotlin 的基本数据类型分为数值型(Byte、Short、Int、Long、Float、Double)、布尔型(Boolean)、字符型(Char)和字符串型(String)。与 Java 不同,Kotlin 中所有类型都是对象,没有原始类型与包装类型的区分。这意味着你可以直接调用类型的方法,例如 10.toString()。Kotlin 的类型系统还引入了空安全设计,通过 ? 后缀区分可空类型与非空类型,从编译层面杜绝了空指针异常。
| 类型 | 位数 | 取值范围 | Compose 典型应用 |
|---|---|---|---|
| Byte | 8 | -128 ~ 127 | 二进制数据处理 |
| Short | 16 | -32768 ~ 32767 | 节省内存的数值存储 |
| Int | 32 | -2^31 ~ 2^31-1 | 计数、索引、尺寸 |
| Long | 64 | -2^63 ~ 2^63-1 | 时间戳、大数值计算 |
| Float | 32 | 约 ±3.4E38 | 动画插值、透明度 |
| Double | 64 | 约 ±1.7E308 | 精确计算、坐标 |
| Boolean | - | true / false | 条件判断、开关状态 |
| Char | 16 | Unicode 字符 | 单字符输入 |
| String | - | 不可变字符序列 | 文本显示、用户输入 |
类型转换与比较
Kotlin 不支持隐式类型转换,必须显式调用转换函数。例如 Int 转 Long 需要使用 toLong()。比较运算中,== 比较结构相等性(内容),=== 比较引用相等性。在 Compose 中,状态比较通常使用 == 判断内容是否变化,以决定是否触发重组。
val intValue: Int = 42
val longValue: Long = intValue.toLong() // 显式转换
val doubleValue: Double = 3.14
val intFromDouble = doubleValue.toInt() // 截断小数部分
// 字符串与数值互转
val numStr = "123"
val num = numStr.toIntOrNull() ?: 0 // 安全转换,失败返回 null
实战场景:Compose 中的数值处理
在 UI 开发中,经常需要处理尺寸、颜色、动画等数值。Kotlin 的类型安全特性能够避免许多潜在错误:
@Composable
fun ProgressBar(progress: Float) {
// Float 类型确保进度值在 0f-1f 之间
val percentage = (progress * 100).toInt()
Text("进度: $percentage%")
// 使用 Long 类型处理时间戳
val timestamp = System.currentTimeMillis()
val formattedTime = timestamp.toString()
}
常见误区与总结
- 误区一:混淆
==与===。在 Compose 中,状态比较应使用==判断内容变化。 - 误区二:忽略空安全。可空类型必须进行空处理,否则编译不通过。
- 误区三:过度使用 Float 导致精度丢失。涉及金额等精确计算时应使用 Double 或 BigDecimal。
2.3 字符串模板与表达式
核心原理
字符串模板是 Kotlin 的一大亮点,允许在字符串中直接嵌入变量和表达式,使用 $ 符号引用变量,使用 ${} 包裹复杂表达式。这一特性极大地简化了字符串拼接操作,使代码更加简洁易读。在 Compose 中,字符串模板常用于动态文本展示、格式化输出等场景。
val name = "Kotlin"
val version = 2.0
val message = "欢迎使用 $name 版本 ${version}"
// 复杂表达式
val price = 99.99
val discount = 0.8
val finalPrice = "折后价: ${"$"}${String.format("%.2f", price * discount)}"
// 转义字符
val path = "C:\\Users\\Admin" // 使用双反斜杠
val rawString = """多行文本
使用原始字符串
保留格式"""
实战场景:Compose 中的动态文本
在 UI 中,字符串模板让文本内容与状态变量紧密结合,实现响应式更新:
@Composable
fun UserGreeting(userName: String, loginCount: Int) {
Text(
text = "欢迎回来,$userName!\n这是您第 ${loginCount + 1} 次登录",
style = MaterialTheme.typography.bodyLarge
)
// 条件表达式嵌入
val status = if (loginCount > 10) "VIP用户" else "普通用户"
Text("会员等级: $status")
}
常见误区与总结
- 误区一:忘记转义
$符号。在字符串模板中,$是特殊字符,如需显示字面量应使用\$。 - 误区二:过度使用模板导致可读性下降。当表达式过于复杂时,应提取为独立变量。
- 误区三:忽略多行字符串的缩进处理。原始字符串会保留所有空格和换行。
2.4 条件表达式与 when 分支
核心原理
Kotlin 的 if 是表达式而非语句,可以返回值。when 则替代了 Java 的 switch,但功能更强大:支持任意类型、范围判断、条件组合,同样可以作为表达式使用。在 Compose 中,条件表达式和 when 分支是构建动态 UI 的核心工具,用于根据状态决定渲染内容。
// if 作为表达式
val max = if (a > b) a else b
// when 表达式
fun describe(obj: Any): String = when (obj) {
1 -> "数字一"
"Hello" -> "问候语"
is Long -> "长整型"
!is String -> "不是字符串"
else -> "未知类型"
}
// when 范围判断
val score = 85
val grade = when (score) {
in 90..100 -> "优秀"
in 80..89 -> "良好"
in 60..79 -> "及格"
else -> "不及格"
}
实战场景:Compose 中的条件渲染
在 Compose 中,条件表达式控制 UI 的显示与隐藏,when 分支处理多状态切换:
@Composable
fun LoadStateView(state: LoadState) {
when (state) {
is LoadState.Loading -> CircularProgressIndicator()
is LoadState.Success -> ContentList(state.data)
is LoadState.Error -> ErrorMessage(state.message)
}
}
@Composable
fun PriceDisplay(price: Double, isMember: Boolean) {
val displayPrice = if (isMember) price * 0.9 else price
Text(
text = if (displayPrice > 100) "高价商品: $displayPrice" else "优惠价格: $displayPrice",
color = if (displayPrice > 100) Color.Red else Color.Green
)
}
常见误区与总结
- 误区一:忘记 when 的穷尽性检查。当 when 作为表达式时,必须覆盖所有可能分支或提供 else。
- 误区二:在 if 表达式中使用多条语句。if 作为表达式时,最后一个表达式作为返回值。
- 误区三:过度嵌套条件。应使用 when 或提前返回简化逻辑。
2.5 循环控制:for、while 与迭代器
核心原理
Kotlin 提供 for、while、do-while 三种循环结构。for 循环主要用于遍历集合、数组或范围,语法简洁;while 适用于条件控制的循环。在 Compose 中,循环常用于生成列表、处理集合数据,但需要注意避免在 Composable 函数中执行耗时循环操作。
// for 循环遍历范围
for (i in 1..5) { println(i) } // 包含 5
for (i in 1 until 5) { println(i) } // 不包含 5
for (i in 10 downTo 1 step 2) { println(i) } // 递减步长
// 遍历集合
val fruits = listOf("苹果", "香蕉", "橙子")
for (fruit in fruits) { println(fruit) }
for ((index, fruit) in fruits.withIndex()) { println("$index: $fruit") }
// while 循环
var count = 0
while (count < 5) {
println("计数: $count")
count++
}
实战场景:Compose 中的列表渲染
在 Compose 中,循环通常与 LazyColumn 或 forEach 结合使用,但需要注意性能优化:
@Composable
fun NumberList(numbers: List) {
// 使用 forEach 处理集合
Column {
numbers.forEach { number ->
Text("数字: $number")
}
}
// 使用 LazyColumn 高效渲染大量数据
LazyColumn {
items(numbers) { number ->
ListItem(number)
}
}
}
// 循环中的条件控制
fun processNumbers(numbers: List): List {
val result = mutableListOf()
for (number in numbers) {
if (number % 2 == 0) continue // 跳过偶数
if (number > 100) break // 超过 100 终止
result.add(number)
}
return result
}
常见误区与总结
- 误区一:在 Compose 重组中使用大循环。应使用
remember缓存计算结果。 - 误区二:混淆
until与..的范围边界。 - 误区三:在循环中修改正在遍历的集合,可能导致并发修改异常。