1. Kotlin基础回顾:变量与常量、空安全机制、函数与Lambda表达式、数据类与密封类

各位同学,欢迎来到《Kotlin Android MVVM与Clean架构实战》的第一章。

说实话,每次带新人或者做技术分享,我都会从Kotlin基础开始讲。为什么?因为很多坑,其实都出在基础不牢上。我自己刚转Kotlin那会儿,也踩过不少雷。今天这一章,咱们就把地基打扎实。

本章核心知识图谱

Kotlin 基础核心 变量与常量 val / var 类型推断 lateinit / lazy 空安全机制 ? 可空类型 !! 非空断言 ?: Elvis 操作符 函数与Lambda 函数声明 / 默认参数 高阶函数 Lambda 表达式 数据类与密封类 data class 自动生成 sealed class 有限状态 when 表达式穷举 MVVM + Clean 架构的基石

1.1 变量与常量:val 和 var 的选择

在Kotlin里,声明变量有两种方式:valvar。很多人觉得这有啥好讲的?不就是可变和不可变嘛。但我在项目中见过太多滥用 var 的情况了。

val 是只读引用,一旦赋值就不能再改。注意,我说的是「引用」不能改,如果它指向一个对象,对象内部的状态是可以变的。var 才是真正的可变变量。

我的习惯:能用 val 就用 val。你想想看,一个变量如果永远不会被重新赋值,代码的可读性和安全性都会提升。我重构老项目时,第一步就是把能改成 val 的 var 全改掉。

// 正确的用法
val name: String = "Kotlin"   // 只读,不能重新赋值
var count: Int = 0            // 可变

// 注意:val 指向的对象内部可以变化
val list = mutableListOf<Int>(1, 2, 3)
list.add(4)  // 这是允许的
// list = mutableListOf(5, 6, 7)  // 编译错误!

再说说 lateinitlazy。这两个都是延迟初始化的手段,但适用场景完全不同。

特性 lateinit lazy
适用类型 var 可变属性 val 只读属性
初始化时机 手动赋值 首次访问时自动执行
线程安全 不保证 默认线程安全
常见场景 依赖注入、Activity/Fragment 中的 View 耗时计算、单例

我曾经踩过的坑:在 Activity 里用 lateinit 声明了一个 View 变量,结果在 onCreate 之前就访问了它,直接抛 UninitializedPropertyAccessException。记住,lateinit 只是延迟了声明,不是延迟了初始化,你得确保在使用前已经赋值。

1.2 空安全机制:Kotlin 最让我安心的设计

说实话,Java 里最让我头疼的就是 NullPointerException。Kotlin 的空安全机制,说白了就是把这个运行时问题提到了编译期。我个人觉得,这是 Kotlin 最值得吹爆的特性。

Kotlin 的类型系统默认不允许空值。如果你想允许一个变量为 null,必须显式地在类型后面加 ?

// 不可空类型
var name: String = "Hello"
// name = null  // 编译错误!

// 可空类型
var nullableName: String? = "Hello"
nullableName = null  // 没问题

// 安全调用操作符 ?.
val length = nullableName?.length  // 如果 nullableName 为 null,结果为 null

// Elvis 操作符 ?:
val lengthOrZero = nullableName?.length ?: 0  // 为 null 时返回 0

// 非空断言 !! (慎用!)
val forcedLength = nullableName!!.length  // 为 null 时抛 NPE

核心原则:能用 ?. 就别用 !!。我见过太多人为了省事直接上 !!,结果线上崩得稀里哗啦。你想想看,Kotlin 费这么大劲帮你避免 NPE,你一个 !! 又全给找回来了。

还有一个常用的操作符是 let。配合安全调用,可以优雅地处理可空对象:

nullableName?.let { 
    // 这里面的 it 一定不为 null
    println("名字长度是 ${it.length}")
}

1.3 函数与 Lambda 表达式

Kotlin 的函数比 Java 灵活得多。我个人最喜欢的是默认参数和命名参数,写工具类的时候特别爽。

// 带默认参数的函数
fun greet(name: String, greeting: String = "你好") {
    println("$greeting, $name")
}

// 调用时可以省略有默认值的参数
greet("小明")           // 输出:你好, 小明
greet("小明", "Hello")  // 输出:Hello, 小明

// 命名参数,提高可读性
greet(greeting = "嗨", name = "小红")

Lambda 表达式是 Kotlin 函数式编程的核心。说白了,Lambda 就是一段可以作为参数传递的代码块。

// Lambda 的基本语法
val sum: (Int, Int) -> Int = { x, y -> x + y }
println(sum(3, 5))  // 输出 8

// 如果 Lambda 是函数的最后一个参数,可以提到括号外面
val numbers = listOf(1, 2, 3, 4, 5)
numbers.filter { it > 2 }
       .map { it * 2 }
       .forEach { println(it) }  // 输出 6, 8, 10

高阶函数实战:在 MVVM 架构中,我们经常用 Lambda 来做回调。比如 ViewModel 暴露一个 LiveData,Fragment 通过观察者模式来响应数据变化。这背后其实就是高阶函数的思想。

1.4 数据类与密封类

数据类 data class 是我在项目中用得最多的特性之一。你想想看,Java 里写一个 POJO 要生成多少样板代码?getter、setter、toString、equals、hashCode……Kotlin 一行搞定。

data class User(
    val id: Long,
    val name: String,
    val email: String? = null
)

// 自动生成了以下方法:
// - toString():User(id=1, name=Alice, email=alice@example.com)
// - equals() / hashCode():基于主构造函数中的所有属性
// - copy():方便复制并修改部分属性
// - componentN():解构声明

val user = User(1, "Alice")
val copyUser = user.copy(name = "Bob")
println(copyUser)  // User(id=1, name=Bob, email=null)

注意:data class 的主构造函数中至少有一个参数。而且,自动生成的方法只基于主构造函数中的属性。如果你在类体中声明属性,它们不会被包含在 equals、hashCode 和 toString 中。我曾经因为这个排查了半天 bug,大家引以为戒。

密封类 sealed class 是 Kotlin 中一个非常强大的概念。它用来表示受限的类层次结构,说白了就是「这个类的子类是有限的,就这么多」。配合 when 表达式使用,可以实现穷举检查。

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("成功:${result.data}")
        is Result.Error -> println("失败:${result.message}")
        Result.Loading -> println("加载中...")
        // 不需要 else 分支,因为已经穷举了所有可能
    }
}

在 MVVM 架构中,我经常用密封类来表示 UI 状态。比如一个页面的加载状态,无非就是加载中、成功、失败这三种。用密封类来建模,编译器会帮你检查是否处理了所有情况,非常安全。

总结一下:数据类帮我们省去了大量样板代码,密封类帮我们构建安全的有限状态机。这两个特性在 Clean 架构中会频繁出现,尤其是定义 Repository 的返回结果或者 UI 状态时。

好了,第一章的内容就到这里。这些基础概念虽然简单,但它们是后续所有高级特性的基石。我个人建议,如果你对某个点还不太熟悉,最好打开 IDE 敲一遍。代码这东西,光看是学不会的。


公众号:蓝海资料掘金营,微信deep3321