在学习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
如果:
需要不可变性、线程安全、或初始化逻辑复杂且需要懒加载。