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 核心教程仓库


原始标题:Lazy Initialization in Kotlin