Kotlin 语法要点

要点

Compose 代码常混合 lambda、命名参数和属性委托。先识别“传入一个函数”与“立即调用函数”,再看界面代码。

目录


变量与函数

val name = "Compose"      // val:只读引用(推荐默认使用),类型自动推断为 String
var count = 0             // var:可变引用,Compose 中尽量少用

fun add(a: Int, b: Int = 10): Int {   // 参数可带默认值,返回类型在冒号后
    return a + b
}

fun double(x: Int): Int = x * 2       // 单表达式函数:省略 return 与花括号

类型写在变量名后面,用冒号分隔;分号通常可以省略;String 与 String? 是不同类型。val 只禁止重新赋值,不保证其引用的列表或对象不可变;var 本身也不会触发 Compose 重组,界面要观察 State、StateFlow 等状态容器。


空安全

Kotlin 类型系统在编译期区分可空与非空,这是它相对 Java 最大的安全改进:

var nickname: String? = null     // String? 表示可空类型

// 1. 安全调用 ?.:为 null 时整条表达式返回 null,不抛异常
val length = nickname?.length            // 类型是 Int?

// 2. Elvis 操作符 ?::为 null 时给默认值
val display: String = nickname ?: "匿名"

// 3. 安全调用 + Elvis 链式组合
val upper: String = nickname?.uppercase() ?: ""

// 4. 非空断言 !!:强行声明"它一定不为 null",为 null 时抛 NPE——慎用
val forced: String = nickname!!

经验法则:Compose 中优先用非空类型 + 默认值建模状态,把可空性留给”暂时没有”的语义(如尚未选中的用户)。


data class 与状态建模

data class 自动生成 equals/hashCode/toString/copy,是承载 UI 状态的标准载体:

data class Task(
    val id: Long,
    val title: String,
    val done: Boolean = false
)

// copy:生成修改了部分构造属性的新实例;这是浅拷贝
val t1 = Task(id = 1, title = "学 Compose")
val t2 = t1.copy(done = true)

// 解构声明
val (id, title, done) = t2

// 默认 equals 比较主构造函数中的属性,不包含类体里另声明的属性
println(t1 == Task(1, "学 Compose"))   // true

data class 适合把相关状态作为一个值传递,但它不自动让对象不可变,也不保证 Compose 跳过重组。copy 内含的可变列表仍共享;Compose 是否跳过还取决于状态观察、参数稳定性与编译器模式。先做到“创建新状态后交给可观察容器”,再根据性能证据优化。参见 Kotlin data class 规则 和 Compose strong skipping。


sealed interface 与枚举状态

当一个值只能取有限几种形态时,用 sealed interface + when 获得编译器穷尽检查:

sealed interface UiState {
    data object Loading : UiState
    data class Success(val tasks: List<Task>) : UiState
    data class Error(val message: String) : UiState
}

fun render(state: UiState): String = when (state) {
    is UiState.Loading -> "加载中"
    is UiState.Success -> "共 ${state.tasks.size} 条任务"
    is UiState.Error   -> "出错了: ${state.message}"
    // 不需要 else 分支——编译器保证覆盖所有子类
}

enum 的每个条目是固定实例,也可以声明属性和方法;sealed 的不同子类型可以拥有不同结构和任意多个实例,例如不同错误消息。只有固定选项时用 enum;不同状态所需的数据不同,用 sealed 可以减少无效组合。

📖 密封类、可见性等修饰符的完整字典见 Kotlin 关键字详解。


扩展函数

扩展函数给既有类型”外挂”新方法,无需继承或修改原类:

// 给 String 添加扩展函数
fun String.truncate(max: Int): String {
    require(max >= 0) { "max must be non-negative" }
    return if (length <= max) this else take(max) + "..."
}

println("Jetpack Compose".truncate(10))   // Jetpack Co...

// Compose 生态实例:给 Modifier 链式扩展(这就是 Modifier 的工作原理)
fun Modifier.cardStyle(): Modifier = this
    .padding(8.dp)
    .clip(RoundedCornerShape(12.dp))

扩展函数的调用外形像成员,但它按接收者的声明类型静态解析,不能覆盖真正成员,也不能访问类的私有成员。这里的 length/take 按 UTF-16 单元计数,不能作为面向任意 emoji 的用户可见截断器。规则见 Kotlin 扩展文档。


Lambda 与尾随 lambda

函数在 Kotlin 中是一等公民,函数类型写作 (参数类型) -> 返回类型:

val sum: (Int, Int) -> Int = { a, b -> a + b }

// 尾随 lambda:函数的最后一个参数是 lambda 时,可把 lambda 挪到括号外
fun onClick(handler: () -> Unit) { handler() }
onClick { println("clicked") }        // 等价于 onClick(handler = { ... })

// 单参数 lambda 默认命名为 it
listOf(1, 2, 3).map { it * 2 }        // [2, 4, 6]

这就是 Compose 组件调用的形态来源:

Button(onClick = { /* 处理点击 */ }) {
    Text("提交")                        // content: @Composable () -> Unit 尾随 lambda
}

作用域函数速览

作用域函数同时区别接收者写法、返回值与调用形式;不要只记 this/it 而忽略表达式的最终结果:

函数引用方式返回值典型场景
letitlambda 结果空安全分支:user?.let { ... }
applythis对象自身构建配置:Intent().apply { ... }
alsoit对象自身附加副作用:日志、登记
runthislambda 结果计算并返回结果
withthislambda 结果对同一对象的连续操作
// let + 安全调用:仅当 nickname 非空时执行
nickname?.let { name -> greetingText = "你好, $name" }

// apply:配置对象并返回它自身
val intent = Intent(context, DetailActivity::class.java).apply {
    putExtra("id", taskId)
}

练习与实践

一个不用 Android 的完整验收程序

保存为 Main.kt,运行 kotlinc Main.kt -include-runtime -d main.jar、java -jar main.jar。下面的断言把 val、浅拷贝、默认值和回调时序放在一起,任何错误都会使程序失败。只有此完整程序绑定到本轮运行证据,上面的界面片段仍需在 Compose 工程验收。

data class Draft(val title: String, val tags: MutableList<String>)

fun normalizeTitle(raw: String?): String? = raw?.trim()?.takeIf { it.isNotEmpty() }
fun runAction(action: () -> Unit) { action() }

fun main() {
    val original = Draft("read", mutableListOf("kotlin"))
    val shallow = original.copy(title = "write")
    shallow.tags.add("shared")
    check(original.title == "read")
    check(original.tags == listOf("kotlin", "shared"))
    val detached = original.copy(tags = original.tags.toMutableList())
    detached.tags.clear()
    check(original.tags.size == 2)
    check(normalizeTitle(null) == null)
    check(normalizeTitle("   ") == null)
    check(normalizeTitle(" learn ") == "learn")
    var calls = 0
    val callback: () -> Unit = { calls += 1 }
    check(calls == 0)
    runAction(callback)
    check(calls == 1)
    // runAction(callback()) 不编译:callback() 已执行并返回 Unit,不是函数。
    println("Kotlin syntax contracts passed")
}

基础练习

  • [ ] 写一个 data class User(name: String, age: Int),练习 copy 与解构
  • [ ] 把一个 String? 依次用 ?.、?:、!! 三种方式处理,观察编译器提示
  • [ ] 为 List<Int> 写扩展函数 fun List<Int>.second(): Int?,返回第二个元素
  • [ ] 用 when + sealed interface 表达”表单校验状态”:空 / 校验中 / 通过 / 失败(原因)

进阶挑战

  • [ ] 不查资料,解释 Button(onClick = { ... }) { Text("OK") } 中两个 lambda 各自对应哪个参数
  • [ ] 用 apply 重写一段”创建后逐个 set 属性”的 Java 风格代码
  • [ ] 给 Draft 的 tags 增加元素,解释为什么 copy 后原对象也变了;然后改为传入独立列表,并通过上面的断言。

相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇