Kotlin 密封类与延迟初始化学习笔记

Kotlin 密封类与延迟初始化学习笔记


目录

  1. [密封类 Sealed Class](#密封类 Sealed Class)
  2. 延迟初始化
  3. [lateinit vs lazy](#lateinit vs lazy)

1. 密封类 Sealed Class

什么是密封类?

密封类是一种受限的类继承结构,所有子类必须在同一个文件中声明。

为什么需要密封类?

场景 问题 解决
when 表达式 用 enum 时子类数量固定 枚举
when 表达式 用普通类时无法穷尽检查 密封类

sealed vs enum

对比 enum sealed
子类数量 固定 可任意数量
子类状态 每个枚举值是单例 可带构造参数
扩展性 不能继承 enum 不能继承 sealed
使用场景 有限集合 类型安全分支

基本语法

kotlin 复制代码
// sealed 修饰符
sealed class Result {
    class Success(val data: String) : Result()
    class Error(val message: String) : Result()
    object Loading : Result()  // object 表示单例
}

使用示例

kotlin 复制代码
// 定义密封类
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,因为所有情况都已覆盖
    }
}

密封类的特点

kotlin 复制代码
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 状态
kotlin 复制代码
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)
}
场景二:命令模式
kotlin 复制代码
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()
    }
}
场景三:导航结果
kotlin 复制代码
sealed class NavigationResult {
    object Success : NavigationResult()
    object Cancelled : NavigationResult()
    data class Error(val code: Int) : NavigationResult()
}

2. 延迟初始化

为什么需要延迟初始化?

kotlin 复制代码
class MyActivity : AppCompatActivity() {
    private var adapter: MyAdapter  // 不能直接赋值

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // adapter 必须在 setContentView() 之后才能初始化
        adapter = MyAdapter()  // 太晚了?或者太早了?
    }
}

lateinit

kotlin 复制代码
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 规则

kotlin 复制代码
// 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 使用场景

kotlin 复制代码
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 延迟属性

kotlin 复制代码
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 线程安全

kotlin 复制代码
// 默认线程安全
val db1 by lazy { Database() }

// 非线程安全
val db2 by lazy(LazyThreadSafetyMode.NONE) { Database() }

// PUBLICATION:多个线程可能初始化多次(最终取其中一个)
val db3 by lazy(LazyThreadSafetyMode.PUBLICATION) { Database() }

场景对比

kotlin 复制代码
// 场景一: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()
    }
}

常见错误

kotlin 复制代码
// 错误 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()  // ✓

最佳实践

kotlin 复制代码
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

相关推荐
一木 之林6 小时前
Dify学习笔记 03 · RAG检索增强生成:三代范式、评估与落地(第40-45、55-56集)
笔记·小程序·交互·dify
apple-mapping7 小时前
网络RTK定位学习总结
学习·网络rtk
楚山之石7 小时前
【Lancet Neurology 】多发性硬化认知障碍的 SDMT 筛查、灰质萎缩标志与认知康复循证路径
图像处理·人工智能·学习·mri
海海不掉头发9 小时前
CSDN博客_时序预测9模型复现对比
人工智能·学习·软件工程
蓝祁子yoky11 小时前
Qt学习日记 Day13:网络编程(一)-TCP基础与Socket实战
网络·qt·学习
箓维11 小时前
TCP Socket的进阶实现
网络·笔记·tcp/ip
二哈赛车手11 小时前
新人笔记---hermes定时功能设计,具体讲解以及可优化点
java·人工智能·笔记·spring·ai·typescript
架构师伯子明12 小时前
Ros 学习十三:IK Demo
学习
小羊没烦恼!12 小时前
关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
java·服务器·网络·学习·负载均衡
幼儿园蛋小黄12 小时前
从 STM32N647 的 AI 部署入门嵌入式:一条可落地的学习路线
人工智能·stm32·学习