Kotlin 密封类与延迟初始化学习笔记
目录
- [密封类 Sealed Class](#密封类 Sealed Class)
- 延迟初始化
- [lateinit vs lazy](#lateinit vs lazy)
1. 密封类 Sealed Class
什么是密封类?
密封类是一种受限的类继承结构,所有子类必须在同一个文件中声明。
为什么需要密封类?
| 场景 |
问题 |
解决 |
| when 表达式 |
用 enum 时子类数量固定 |
枚举 |
| when 表达式 |
用普通类时无法穷尽检查 |
密封类 |
sealed vs enum
| 对比 |
enum |
sealed |
| 子类数量 |
固定 |
可任意数量 |
| 子类状态 |
每个枚举值是单例 |
可带构造参数 |
| 扩展性 |
不能继承 enum |
不能继承 sealed |
| 使用场景 |
有限集合 |
类型安全分支 |
基本语法
// sealed 修饰符
sealed class Result {
class Success(val data: String) : Result()
class Error(val message: String) : Result()
object Loading : Result() // object 表示单例
}
使用示例
// 定义密封类
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// when 表达式(编译器会检查是否穷尽)
fun handleResult(result: Result<String>) {
when (result) {
is Result.Success -> println("成功: ${result.data}")
is Result.Error -> println("失败: ${result.message}")
is Result.Loading -> println("加载中...")
// 不需要 else,因为所有情况都已覆盖
}
}
密封类的特点
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
// 1. 不能被外部实例化
// val e = Expr() // 错误
// 2. 子类必须在这个文件里定义
class MyExpr : Expr() // 错误:不能继承 sealed 类
// 3. 子类可以有不同的形态
val num = Expr.Num(10) // 带参数
val sum = Expr.Sum(num, Expr.Num(20)) // 嵌套
应用场景
场景一:UI 状态
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val msg: String) : UiState<Nothing>()
}
// 使用
val state: UiState<List<String>> = UiState.Success(listOf("A", "B"))
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data) // data 类型已知
is UiState.Error -> showError(state.msg)
}
场景二:命令模式
sealed class Command {
class Add(val item: String) : Command()
class Remove(val index: Int) : Command()
object Clear : Command()
}
fun execute(command: Command) {
when (command) {
is Command.Add -> addItem(command.item)
is Command.Remove -> removeItem(command.index)
is Command.Clear -> clearAll()
}
}
场景三:导航结果
sealed class NavigationResult {
object Success : NavigationResult()
object Cancelled : NavigationResult()
data class Error(val code: Int) : NavigationResult()
}
2. 延迟初始化
为什么需要延迟初始化?
class MyActivity : AppCompatActivity() {
private var adapter: MyAdapter // 不能直接赋值
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// adapter 必须在 setContentView() 之后才能初始化
adapter = MyAdapter() // 太晚了?或者太早了?
}
}
lateinit
class MyActivity : AppCompatActivity() {
private lateinit var adapter: MyAdapter // 延迟初始化声明
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 在合适的时候初始化
adapter = MyAdapter()
}
fun getAdapter() = adapter // 使用时保证已初始化
}
lateinit 规则
// 1. 只能修饰 var
lateinit var name: String // ✓
lateinit val age: Int // ✗ val 不可变
// 2. 不能用于基本类型(Int、Boolean 等)
lateinit var count: Int // ✗ 基本类型用 var + 延迟
lateinit var name: String // ✓ 对象类型
// 3. 必须手动初始化
lateinit var adapter: MyAdapter
// adapter.doSomething() // 错误:还没初始化
// 4. 判断是否已初始化
if (::adapter.isInitialized) {
adapter.doSomething()
}
lateinit 使用场景
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: FruitAdapter
private lateinit var toolbar: MaterialToolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化 ViewBinding
binding = ActivityMainBinding.inflate(layoutInflater)
// 初始化控件
recyclerView = findViewById(R.id.recyclerView)
adapter = FruitAdapter(dataList)
toolbar = findViewById(R.id.toolbar)
}
}
3. lateinit vs lazy
对比表
| 特性 |
lateinit |
lazy |
| 修饰符 |
var |
val |
| 首次访问 |
手动初始化 |
自动初始化 |
| 线程安全 |
否 |
是(默认) |
| 基本类型 |
不可用 |
可用(by lazy { 0 }) |
| 内存 |
保留声明时的内存 |
只在首次访问时分配 |
lazy 延迟属性
class User {
// val 不可变,用 lazy 延迟初始化
val database: Database by lazy {
println("初始化数据库...")
Database()
}
val config: Map<String, String> by lazy {
loadConfig()
}
}
// 第一次访问时才初始化
val user = User()
println(user.database) // 输出: 初始化数据库...,然后返回 Database 实例
println(user.database) // 直接返回已有的实例,不再打印
lazy 线程安全
// 默认线程安全
val db1 by lazy { Database() }
// 非线程安全
val db2 by lazy(LazyThreadSafetyMode.NONE) { Database() }
// PUBLICATION:多个线程可能初始化多次(最终取其中一个)
val db3 by lazy(LazyThreadSafetyMode.PUBLICATION) { Database() }
场景对比
// 场景一:Activity/Fragment 中的 View(用 lateinit)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) // 必须手动
}
}
// 场景二:单例或配置(用 lazy)
class App : Application() {
val database by lazy { Database.getInstance() }
val config by lazy { Config.load() }
}
// 场景三:RecyclerView Adapter(用 lateinit)
class MainActivity : AppCompatActivity() {
private lateinit var adapter: FruitAdapter
override fun onCreate(...) {
adapter = FruitAdapter(dataList) // 手动
}
}
// 场景四:复杂对象的延迟加载(用 lazy)
class UserProfile {
// 头像图片较大,延迟加载
val avatarBitmap by lazy {
BitmapFactory.decodeFile(avatarPath)
}
// 用户相册可能很大,按需加载
val photoGallery by lazy {
loadPhotosFromDisk()
}
}
常见错误
// 错误 1:val 用 lateinit
lateinit val name: String // ✗
// 错误 2:基本类型用 lateinit
lateinit var count: Int // ✗
// 错误 3:未初始化就使用
lateinit var adapter: MyAdapter
adapter.notifyDataSetChanged() // ✗ 可能崩溃
// 正确:初始化后才能使用
lateinit var adapter: MyAdapter
adapter = MyAdapter() // 先初始化
adapter.notifyDataSetChanged() // ✓
最佳实践
class MyActivity : AppCompatActivity() {
// ViewBinding - 必须用 lateinit(var)
private lateinit var binding: ActivityMainBinding
// 只读配置 - 用 lazy
private val apiUrl by lazy { BuildConfig.API_URL }
private val maxCacheSize by lazy { 100 * 1024 * 1024 }
// 数据适配器 - 用 lateinit(可能需要重新创建)
private lateinit var adapter: FruitAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
adapter = FruitAdapter(emptyList())
recyclerView.adapter = adapter
}
}
附录:选型指南
什么时候用 sealed?
| 场景 |
推荐 |
| 有限状态(加载/成功/失败) |
sealed |
| 导航结果 |
sealed |
| 命令模式 |
sealed |
| 配置选项 |
enum(更简单) |
什么时候用 lateinit?
| 场景 |
推荐 |
| ViewBinding |
lateinit var |
| Adapter |
lateinit var |
| 需要重新赋值的对象 |
lateinit var |
什么时候用 lazy?
| 场景 |
推荐 |
| 单例实例 |
by lazy |
| 配置常量 |
by lazy |
| 重量级对象(数据库、Bitmap) |
by lazy |
| 只读属性 |
by lazy |