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

相关推荐
xian_wwq8 小时前
【学习笔记】-深度认知系列-第2讲-大模型到底是什么?——拆解“参数、训练、推理”
笔记·学习·深度认知
lzhdim9 小时前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
Oll Correct11 小时前
Adobe illustrator 案例九:百宝箱图标绘制
笔记·ui·adobe·illustrator
那年窗外下的雪.11 小时前
AIDC 学习日志|第 15 天|EVPN 多归属故障收敛与撤销机制
网络·学习·php
M78佐菲12 小时前
Linux学习笔记:进程
linux·笔记·学习·算法
我命由我1234513 小时前
人脸识别 - 勒克斯(光线照射到物体表面上的明亮程度)
android·java·学习·java-ee·人脸识别·学习方法·android runtime
乐橙开放平台14 小时前
养老 SaaS 笔记:setMessageCallback 事件桥接 + msgType 映射 P0/P1,先 ACK 再分流值班流
笔记·物联网·音视频·智能家居
Logintern0915 小时前
【每天学习一点点】MPI是什么?
学习·mpi·多机训练
fengci.15 小时前
提示词注入学习小结
学习
晓梦林15 小时前
Tools2靶场学习笔记
笔记·学习