在学习kotlin的过程中遇到lateinit var 和 val by lazy,他们都用于延迟初始化,但是对它们的使用场景又有些模糊。问了一下AI感觉回答的还挺清晰,记录一下
1. 核心区别
| 特性 | lateinit var | val by lazy |
|---|---|---|
| 可变性 | 可变 (var) | 不可变 (val) |
| 初始化时机 | 显式初始化(开发者手动赋值) | 首次访问时自动初始化(懒加载) |
| 线程安全 | 需要开发者自行保证线程安全 | 默认线程安全(可配置) |
| 适用类型 | 仅限非空类型(不能是原始类型) | 支持所有类型(包括原始类型) |
| 异常风险 | 未初始化时访问会抛出 UninitializedPropertyAccessException | 初始化代码块中的异常会被缓存并重复抛出 |
| 是否可重新初始化 | 可重新赋值 | 一旦初始化,值不可变 |
2. 典型使用场景
lateinit var 适用场景
-
依赖注入
当变量需要由框架(如 Dagger、Koin 或 Spring)在运行时注入时:class MyService { lateinit var database: Database } -
Android 的 View 初始化
Activity/Fragment 的视图需要在onCreate或onCreateView中初始化:class MainActivity : AppCompatActivity() { lateinit var recyclerView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) recyclerView = findViewById(R.id.recycler_view) } } -
单元测试的 Setup
在测试类的@Before方法中初始化:class MyTest { lateinit var httpClient: HttpClient @Before fun setup() { httpClient = HttpClient.create() } } -
需要重新赋值的场景
变量可能在生命周期内被多次初始化:class ConfigManager { lateinit var currentConfig: Config fun updateConfig(newConfig: Config) { currentConfig = newConfig } }
val by lazy 适用场景
-
昂贵的资源初始化
初始化成本高且可能不会被使用的资源:class ImageLoader { val heavyResource: HeavyResource by lazy { HeavyResource.loadFromDisk() // 仅在第一次访问时加载 } } -
不可变单例或共享状态
需要线程安全的单例或共享对象:class AppSettings { val instance: AppSettings by lazy { AppSettings() } } -
依赖其他属性的初始化
初始化需要依赖对象的其他属性:class UserViewModel(userId: String) { private val userRepository = UserRepository() val user: User by lazy { userRepository.getUser(userId) } } -
配置灵活初始化逻辑
支持自定义初始化逻辑和异常处理:val config: Config by lazy(LazyThreadSafetyMode.NONE) { if (isDebug) DebugConfig() else ProductionConfig() }
3. 关键注意事项
lateinit var
- 必须确保在使用前初始化,否则会抛出异常。
- 可以通过
::myProperty.isInitialized检查是否已初始化。 - 不能用于原始类型(如
Int、Boolean)。
val by lazy
-
初始化代码块 (
{}) 只会在第一次访问时执行。 -
默认线程安全(
LazyThreadSafetyMode.SYNCHRONIZED),可通过参数调整:val data: Data by lazy(LazyThreadSafetyMode.NONE) { ... } // 非线程安全 -
如果初始化代码抛出异常,后续访问会重复抛出同一异常。
4. 代码示例对比
使用 lateinit var
class NetworkClient {
lateinit var endpoint: String
fun init(endpoint: String) {
this.endpoint = endpoint
}
fun sendRequest() {
if (::endpoint.isInitialized) {
println("Sending request to $endpoint")
}
}
}
使用 val by lazy
class AppConfig {
val serverUrl: String by lazy {
val env = System.getenv("APP_ENV")
if (env == "prod") "https://api.prod.com" else "https://api.dev.com"
}
fun logConfig() {
println("Server URL is $serverUrl") // 首次调用时初始化
}
}
5. 如何选择?
- 用
lateinit var如果:
需要可变性、显式控制初始化时机,或者变量可能被重新赋值。 - 用
val by lazy如果:
需要不可变性、线程安全、或初始化逻辑复杂且需要懒加载。