2. Kotlin语言基础:为Android开发打下语言基础

Kotlin自2017年成为Android官方支持的一级开发语言以来,已逐步取代Java成为现代Android应用开发的主流选择。与Java相比,Kotlin在语法简洁性、空安全防护、函数式编程支持以及协程并发等方面具有显著优势。本章将系统讲解Kotlin的核心语言要素——变量与数据类型、控制流、函数、面向对象、空安全机制、集合框架与Lambda表达式,帮助读者建立扎实的Kotlin语言功底,为后续Android组件开发、UI构建与业务逻辑实现奠定坚实基础。

Kotlin语言核心知识体系 Kotlin 语言基础 变量与数据类型 控制流 函数 空安全机制 面向对象 集合与Lambda var / val 类型推断 if / when for / while 默认参数 高阶函数 Android 应用开发 Activity · Fragment · ViewModel · Jetpack Compose 空安全:? / !! / ?: / let Lambda:{ x -> x * 2 }

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。

数据类型占用空间取值范围典型应用场景
Byte8位-128 ~ 127二进制数据、文件操作
Int32位-2^31 ~ 2^31-1计数器、ID、索引
Long64位-2^63 ~ 2^63-1时间戳、大数值计算
Float32位约±3.4E38UI动画插值、传感器数据
Double64位约±1.7E308金额计算、科学计算
Boolean1位(逻辑)true/false开关状态、条件判断
Char16位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中创建练习项目,动手实践每个代码示例,并尝试修改参数观察运行结果,以加深理解。