1. 概述
Kotlin 语言中有很多语法糖来提升开发效率,其中延迟初始化(Lazy Initialization)是其一大亮点。它可以帮助我们按需加载对象,避免不必要的资源浪费。
本文将重点介绍 Kotlin 中两个实现延迟初始化的关键字:
lazy
:用于属性的延迟初始化,支持线程安全模式lateinit
:用于声明稍后初始化的非空属性,适用于依赖注入等场景
2. Java 中的延迟初始化模式
在 Java 中,若想实现线程安全的延迟初始化,通常需要使用静态内部类或双重检查锁定等复杂方式。例如,经典的“静态内部类 Holder 模式”:
public class ClassWithHeavyInitialization {
private ClassWithHeavyInitialization() {
}
private static class LazyHolder {
public static final ClassWithHeavyInitialization INSTANCE = new ClassWithHeavyInitialization();
}
public static ClassWithHeavyInitialization getInstance() {
return LazyHolder.INSTANCE;
}
}
该方式利用了 Java 类加载机制的懒加载特性,只有在调用 getInstance()
时才会初始化对象。通过测试可验证其单例行为:
@Test
public void giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall() {
ClassWithHeavyInitialization classWithHeavyInitialization = ClassWithHeavyInitialization.getInstance();
ClassWithHeavyInitialization classWithHeavyInitialization2 = ClassWithHeavyInitialization.getInstance();
assertTrue(classWithHeavyInitialization == classWithHeavyInitialization2);
}
虽然可行,但这种写法在 Java 中显得过于繁琐。延迟初始化本应是个简单的事情,却需要写这么多样板代码。
3. Kotlin 中的 lazy 初始化
Kotlin 原生支持延迟初始化,通过 lazy
函数实现,简洁又安全。
✅ 基本用法
@Test
fun givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce() {
val numberOfInitializations = AtomicInteger()
val lazyValue: ClassWithHeavyInitialization by lazy {
numberOfInitializations.incrementAndGet()
ClassWithHeavyInitialization()
}
println(lazyValue)
println(lazyValue)
assertEquals(numberOfInitializations.get(), 1)
}
如上所示,lazyValue
第一次访问时才会初始化,后续访问返回的是同一个实例。
✅ 线程安全模式
lazy
支持传入线程安全模式参数 LazyThreadSafetyMode
,常见模式如下:
模式 | 说明 |
---|---|
SYNCHRONIZED |
默认模式,线程安全,仅初始化一次 |
PUBLICATION |
多线程并发初始化,最终取第一个返回值 |
NONE |
不做线程控制,不推荐用于多线程环境 |
✅ 示例:使用 PUBLICATION
模式
@Test
fun whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce() {
val numberOfInitializations = AtomicInteger()
val lazyValue: ClassWithHeavyInitialization by lazy(LazyThreadSafetyMode.PUBLICATION) {
numberOfInitializations.incrementAndGet()
ClassWithHeavyInitialization()
}
val executorService = Executors.newFixedThreadPool(2)
val countDownLatch = CountDownLatch(1)
executorService.submit { countDownLatch.await(); println(lazyValue) }
executorService.submit { countDownLatch.await(); println(lazyValue) }
countDownLatch.countDown()
executorService.awaitTermination(1, TimeUnit.SECONDS)
executorService.shutdown()
assertEquals(numberOfInitializations.get(), 2)
}
如上,两个线程同时访问 lazyValue
,由于使用 PUBLICATION
模式,导致初始化被执行了两次。
4. Kotlin 中的 lateinit
⚠️ 问题背景
在 Kotlin 中,非空类型属性必须在声明时或构造函数中初始化。否则编译器会报错:
Kotlin: Property must be initialized or be abstract
但有些场景下,属性是通过框架(如 Dagger、Spring)注入的,无法在声明时初始化。
✅ 解决方案:使用 lateinit
我们可以使用 lateinit
关键字来推迟初始化:
lateinit var a: String
@Test
fun givenLateInitProperty_whenAccessItAfterInit_thenPass() {
a = "it"
println(a)
}
⚠️ 注意事项
只能用于非基本类型(non-primitive):
lateinit var value: Int // ❌ 编译错误
会提示:
Kotlin: 'lateinit' modifier is not allowed on properties of primitive types
若访问未初始化的
lateinit
属性,会抛出异常:@Test(expected = UninitializedPropertyAccessException::class) fun givenLateInitProperty_whenAccessItWithoutInit_thenThrow() { println(a) }
5. 总结
Kotlin 提供了两种非常实用的延迟初始化方式:
特性 | 用途 | 线程安全 | 限制 |
---|---|---|---|
lazy |
属性延迟初始化 | ✅(可配置) | 适用于只读属性 |
lateinit |
延迟赋值 | ❌(需自行控制) | 仅适用于非空非基本类型 |
相比 Java,Kotlin 的实现更加简洁、安全、易用,大大减少了样板代码的编写。
完整代码示例可在 GitHub 上找到:Kotlin 核心教程仓库 ✅