2. Kotlin语言基础:为Android开发打下语言基础
Kotlin自2017年成为Android官方支持的一级开发语言以来,已逐步取代Java成为现代Android应用开发的主流选择。与Java相比,Kotlin在语法简洁性、空安全防护、函数式编程支持以及协程并发等方面具有显著优势。本章将系统讲解Kotlin的核心语言要素——变量与数据类型、控制流、函数、面向对象、空安全机制、集合框架与Lambda表达式,帮助读者建立扎实的Kotlin语言功底,为后续Android组件开发、UI构建与业务逻辑实现奠定坚实基础。
2.1 变量与数据类型
核心原理
Kotlin是静态类型语言,但通过类型推断机制大幅减少了显式类型声明的冗余。变量声明使用两个关键字:val(不可变引用,类似Java的final)和var(可变引用)。Kotlin的基本数据类型包括数值类型(Byte、Short、Int、Long、Float、Double)、字符型(Char)、布尔型(Boolean)以及字符串(String)。与Java不同,Kotlin中所有类型都是对象,没有原始类型的概念,但编译器在底层会进行优化。
判断方法或操作流程
选择val还是var的核心原则:默认使用val,只有当变量确实需要重新赋值时才使用var。这符合函数式编程的不可变理念,能减少并发环境下的状态竞争问题。类型推断的优先级高于显式类型声明,但某些场景(如顶层变量、需要指定父类型时)仍需显式标注。
// 变量声明示例
val appName: String = "MyApp" // 显式类型
val versionCode = 2 // 类型推断为Int
var downloadCount = 1024 // 可变变量
val price: Double = 19.99 // 浮点类型
val isPremium = true // 布尔类型
val firstChar = 'A' // 字符类型
案例拆解或实战场景
在Android开发中,val常用于声明UI组件的引用(如TextView、RecyclerView),因为这些引用在初始化后不会改变;var则用于计数器、进度值、用户输入等需要动态更新的数据。例如,在实现一个下载进度显示功能时:
class DownloadActivity : AppCompatActivity() {
private val progressText: TextView by lazy { findViewById(R.id.progressText) }
private var progress = 0 // 可变进度值
fun updateProgress(newProgress: Int) {
progress = newProgress
progressText.text = "$progress%"
}
}
常见误区或总结复盘
误区1:过度使用var导致代码可读性和安全性下降。建议在数据类、状态类中优先使用val。
误区2:忽略类型推断可能导致的精度问题。例如val x = 10 / 3结果是Int类型的3,而非3.33。
| 数据类型 | 占用空间 | 取值范围 | 典型应用场景 |
|---|---|---|---|
| Byte | 8位 | -128 ~ 127 | 二进制数据、文件操作 |
| Int | 32位 | -2^31 ~ 2^31-1 | 计数器、ID、索引 |
| Long | 64位 | -2^63 ~ 2^63-1 | 时间戳、大数值计算 |
| Float | 32位 | 约±3.4E38 | UI动画插值、传感器数据 |
| Double | 64位 | 约±1.7E308 | 金额计算、科学计算 |
| Boolean | 1位(逻辑) | true/false | 开关状态、条件判断 |
| Char | 16位 | Unicode字符 | 单个字符处理 |
| String | 可变 | 任意字符串 | 文本展示、用户输入 |
2.2 控制流
核心原理
Kotlin的控制流包括条件分支(if、when)和循环(for、while)。与Java最大的区别在于,Kotlin的if和when可以作为表达式使用,即它们可以返回值。这一特性使得代码更加简洁、函数式风格更强。
判断方法或操作流程
when表达式替代了Java的switch,但功能更强大:支持任意类型的分支条件、范围判断、类型判断,且不需要break语句。选择if还是when的经验法则:当分支条件超过两个或条件类型复杂时,优先使用when。
// when表达式示例
fun describeStatus(code: Int): String = when (code) {
200 -> "成功"
404 -> "资源不存在"
500 -> "服务器错误"
in 100..199 -> "信息响应"
else -> "未知状态"
}
// for循环遍历
val numbers = listOf(1, 2, 3, 4, 5)
for (num in numbers) {
println("当前数字: $num")
}
// 带索引遍历
for ((index, value) in numbers.withIndex()) {
println("索引$index 对应值$value")
}
案例拆解或实战场景
在Android的点击事件处理中,when表达式非常适合处理多按钮的点击逻辑。例如在一个设置页面中,根据用户点击的不同选项执行不同操作:
fun handleClick(viewId: Int) {
when (viewId) {
R.id.btn_profile -> navigateToProfile()
R.id.btn_settings -> navigateToSettings()
R.id.btn_about -> showAboutDialog()
else -> Log.w("ClickHandler", "未知控件ID: $viewId")
}
}
常见误区或总结复盘
经验提醒:使用when时务必处理else分支,尤其是在处理枚举或密封类时,否则可能遗漏某些情况导致运行时异常。另外,for循环中修改集合内容会抛出ConcurrentModificationException,应使用迭代器或复制集合。
2.3 函数
核心原理
Kotlin函数是一等公民,支持默认参数、命名参数、高阶函数、Lambda表达式和内联函数。函数可以定义在类内部(成员函数)、类外部(顶层函数)或局部作用域中。函数类型使用(参数类型) -> 返回类型的语法表示。
判断方法或操作流程
设计函数时遵循单一职责原则,参数尽量使用默认值减少重载。当函数逻辑简单且只在一处使用时,可以直接使用Lambda表达式替代函数声明。高阶函数(接收函数作为参数或返回函数)是Kotlin函数式编程的核心。
// 默认参数与命名参数
fun createUser(name: String, age: Int = 18, email: String? = null) {
// 函数体
}
// 调用方式
createUser("张三")
createUser("李四", age = 25, email = "lisi@example.com")
// 高阶函数示例
fun processList(list: List<Int>, operation: (Int) -> Int): List<Int> {
return list.map { operation(it) }
}
// 使用Lambda调用
val doubled = processList(listOf(1, 2, 3)) { it * 2 }
案例拆解或实战场景
在Android的网络请求回调中,高阶函数可以简化异步处理逻辑。例如封装一个网络请求工具:
fun fetchData(onSuccess: (String) -> Unit, onError: (Exception) -> Unit) {
// 模拟网络请求
try {
val result = "模拟数据"
onSuccess(result)
} catch (e: Exception) {
onError(e)
}
}
// 调用
fetchData(
onSuccess = { data -> displayData(data) },
onError = { error -> showError(error.message) }
)
常见误区或总结复盘
性能注意:高阶函数和Lambda会创建额外对象,在性能敏感场景(如RecyclerView的onBindViewHolder)中应使用inline函数消除运行时开销。但inline函数会增加编译后代码体积,需权衡使用。
2.4 面向对象
核心原理
Kotlin的面向对象体系包括类、对象、继承、接口、抽象类、数据类、密封类等。类默认是final的,需要显式使用open关键字允许继承。构造函数分为主构造函数(在类头声明)和次构造函数(使用constructor关键字)。数据类(data class)自动生成equals()、hashCode()、toString()等方法。
判断方法或操作流程
设计类时遵循以下原则:优先使用组合而非继承;数据载体使用data class;状态枚举使用enum class或sealed class;单例使用object关键字。接口可以包含默认实现(Java 8+也支持),但Kotlin的接口属性更灵活。
// 数据类定义
data class User(
val id: Long,
val name: String,
val email: String? = null
)
// 接口与实现
interface Repository {
fun getData(): List<User>
fun saveData(users: List<User>) { /* 默认实现 */ }
}
class UserRepository : Repository {
override fun getData(): List<User> {
return listOf(User(1, "张三"))
}
}
// 单例对象
object DatabaseConfig {
val host = "localhost"
val port = 3306
}
案例拆解或实战场景
在MVVM架构中,数据类用于定义UI状态,密封类用于表示网络请求的三种状态(成功、失败、加载中):
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<User>) : UiState()
data class Error(val message: String) : UiState()
}
// 在ViewModel中使用
class UserViewModel : ViewModel() {
private val _uiState = MutableLiveData<UiState>()
val uiState: LiveData<UiState> = _uiState
fun loadUsers() {
_uiState.value = UiState.Loading
// 模拟加载
_uiState.value = UiState.Success(listOf(User(1, "张三")))
}
}
常见误区或总结复盘
经验提醒:数据类的copy()方法在修改不可变对象时非常有用,但注意浅拷贝问题——嵌套的可变对象仍会被共享。密封类(sealed class)的子类必须定义在同一个文件或同一个包中,这有助于编译器穷举检查。
2.5 空安全机制
核心原理
Kotlin通过类型系统在编译期消除空指针异常(NPE)。可空类型使用?标记,如String?表示可能为null的字符串。访问可空类型的成员时,必须使用安全调用运算符?.、Elvis运算符?:或非空断言!!。
判断方法或操作流程
处理可空类型的优先级顺序:优先使用?.安全调用;需要提供默认值时使用?:;确认非空但编译器无法推断时使用!!(谨慎使用);链式调用时使用let函数限定作用域。
// 空安全操作示例
val name: String? = getUserName()
// 安全调用
val length = name?.length
// Elvis运算符
val displayName = name ?: "匿名用户"
// let函数
name?.let {
println("名字长度: ${it.length}")
}
// 非空断言(谨慎使用)
val upperName = name!!.toUpperCase()
案例拆解或实战场景
在Android的Intent传值场景中,空安全机制尤为重要。从Intent获取数据时,getStringExtra()可能返回null:
// 安全获取Intent数据
val userId = intent.getStringExtra("USER_ID") ?: run {
Toast.makeText(this, "缺少用户ID", Toast.LENGTH_SHORT).show()
finish()
return
}
// 使用let处理非空逻辑
intent.getStringExtra("USER_NAME")?.let { userName ->
tvUserName.text = userName
} ?: run {
tvUserName.text = "未知用户"
}
常见误区或总结复盘
风险提示:过度使用!!会重新引入NPE风险。在团队协作中,应约定!!的使用规范,或通过代码审查控制其使用。另外,Java与Kotlin混编时,Java代码传入的null值不会被Kotlin编译器检测,需要使用平台类型(Platform Type)处理。
2.6 集合与Lambda表达式
核心原理
Kotlin集合框架分为不可变集合(List、Set、Map)和可变集合(MutableList、MutableSet、MutableMap)。Lambda表达式是匿名函数的简写形式,配合集合的高阶函数(map、filter、reduce等)可以写出声明式、函数式的数据处理代码。
判断方法或操作流程
集合操作遵循"链式调用"模式:先用filter筛选,再用map转换,最后用reduce或forEach聚合。注意区分中间操作(返回新集合)和终端操作(返回非集合值)。
// 集合与Lambda综合示例
data class Product(val name: String, val price: Double, val category: String)
val products = listOf(
Product("手机", 2999.0, "电子"),
Product("笔记本", 5999.0, "电子"),
Product("T恤", 99.0, "服装"),
Product("耳机", 499.0, "电子")
)
// 链式操作:筛选电子产品,价格大于500,按价格降序排列
val result = products
.filter { it.category == "电子" }
.filter { it.price > 500 }
.sortedByDescending { it.price }
.map { "${it.name}: ¥${it.price}" }
// 分组统计
val groupByCategory = products.groupBy { it.category }
val totalPrice = products.sumOf { it.price }
案例拆解或实战场景
在Android的搜索功能中,集合操作可以高效处理数据过滤。例如在内存中搜索商品列表:
fun searchProducts(keyword: String): List<Product> {
return products
.asSequence() // 使用序列避免中间集合创建
.filter { it.name.contains(keyword, ignoreCase = true) }
.take(20) // 限制结果数量
.toList()
}
// 在RecyclerView中展示
val searchResults = searchProducts("手机")
adapter.submitList(searchResults)
常见误区或总结复盘
性能优化:处理大量数据时使用asSequence()惰性求值,避免多次创建中间集合。但小数据集(<1000条)时,直接使用集合操作反而更快,因为序列有额外开销。另外,Lambda中修改外部变量需要将其声明为var,但要注意并发安全问题。
| 集合操作 | 功能说明 | 示例 | 返回值 |
|---|---|---|---|
| filter | 筛选满足条件的元素 | list.filter { it > 5 } | 新集合 |
| map | 转换每个元素 | list.map { it * 2 } | 新集合 |
| flatMap | 展平嵌套集合 | list.flatMap { it.items } | 新集合 |
| groupBy | 按键分组 | list.groupBy { it.type } | Map |
| reduce | 累积计算 | list.reduce { acc, i -> acc + i } | 单值 |
| forEach | 遍历每个元素 | list.forEach { println(it) } | Unit |
| sortedBy | 按指定键排序 | list.sortedBy { it.price } | 新集合 |
| distinct | 去重 | list.distinct() | 新集合 |
2.7 综合实战:构建一个简单的用户管理工具
为了将本章知识点融会贯通,我们设计一个用户管理工具,综合运用变量、控制流、函数、面向对象、空安全和集合Lambda特性。
// 数据类定义用户
data class User(
val id: Long,
val name: String,
val email: String?,
val isActive: Boolean = true
)
// 用户管理类
class UserManager {
private val users = mutableListOf<User>()
fun addUser(name: String, email: String?) {
val newUser = User(
id = System.currentTimeMillis(),
name = name,
email = email
)
users.add(newUser)
}
fun findActiveUsers(): List<User> {
return users.filter { it.isActive }
}
fun findUserByName(keyword: String): List<User> {
return users.filter { it.name.contains(keyword, ignoreCase = true) }
}
fun getEmailSummary(): Map<String, Int> {
return users.groupBy {
if (it.email == null) "无邮箱" else "有邮箱"
}.mapValues { it.value.size }
}
fun printSummary() {
users.forEach { user ->
val emailInfo = user.email?.let { "邮箱: $it" } ?: "未绑定邮箱"
println("用户${user.name} - $emailInfo")
}
}
}
// 使用示例
fun main() {
val manager = UserManager()
manager.addUser("张三", "zhangsan@example.com")
manager.addUser("李四", null)
manager.addUser("王五", "wangwu@example.com")
val activeUsers = manager.findActiveUsers()
println("活跃用户数: ${activeUsers.size}")
val summary = manager.getEmailSummary()
println("邮箱统计: $summary")
manager.printSummary()
}
这个案例展示了数据类(面向对象)、可变集合(集合框架)、Lambda表达式(filter、groupBy、mapValues)、空安全(?.、?:、let)以及控制流(forEach)的综合应用。在实际Android项目中,类似的模式广泛存在于ViewModel、Repository和数据管理层中。
本章核心要点回顾:
- 优先使用
val声明不可变变量,减少状态突变 - 善用
when表达式替代复杂的if-else链 - 函数设计遵循单一职责,利用默认参数和命名参数提高可读性
- 数据类与密封类是构建UI状态模型的利器
- 空安全机制是Kotlin最核心的改进,务必掌握
?.、?:、let的配合使用 - 集合高阶函数配合Lambda可以写出声明式、可读性强的数据处理代码
掌握Kotlin语言基础是Android开发的第一步,也是最重要的一步。本章所学的变量、控制流、函数、面向对象、空安全和集合Lambda知识,将在后续的Activity、Fragment、Jetpack组件、协程等高级主题中反复使用。建议读者在Android Studio中创建练习项目,动手实践每个代码示例,并尝试修改参数观察运行结果,以加深理解。