位置:首页 > Kotlin > Kotlin 类:深入解析 lateinit 显式延迟初始化机制

Kotlin 类:深入解析 lateinit 显式延迟初始化机制

时间:2026-08-27  |  作者:风起客  |  阅读:0

目录

  1. 二、lateinit:显式延迟初始化(可变属性专用)
  2. 2.1 基本语法
  3. 2.2 核心特性与限制
  4. 2.1 基本语法与限制
  5. 2.2 适用场景
  6. 2.3 简单示例
1 基本语法 对应的技术说明图
1 基本语法概括基本语法的核心概念、关键要点与实践提示。
二、lateinit:显式延迟初始化(可变属 对应的技术说明图
二、lateinit:显式延迟初始化(可变属概括二、lateinit:显式延迟初始化(可变属的核心概念、关键要点与实践提示。

前言

在 Kotlin 开发中,lateinit 关键字为可变非空属性提供了显式的延迟初始化方案。其语法简洁,仅需在 var 前添加修饰符即可实现后续赋值。该机制允许开发者在对象生命周期内的任意时刻手动指定初始值,特别适用于构造函数无法确定值、需依赖外部资源或动态配置的场景。通过规避可空类型的繁琐检查,lateinit 在保持代码简洁性的同时,实现了高效的内存管理与资源控制,是处理复杂初始化逻辑的重要工具。

Kotlin 类:深入解析 lateinit 显式延迟初始化机制 的核心流程信息图
Kotlin 类:深入解析 lateinit用简体中文信息图概括Kotlin 类:深入解析 lateinit的核心流程、关键规则与实践要点。

二、lateinit:显式延迟初始化(可变属性专用)

2.1 基本语法

在 Kotlin 中,lateinit关键字用于声明一个非空但稍后初始化的可变属性。其基本语法结构非常直观,只需在属性声明前添加 lateinit 修饰符即可。例如,定义一个字符串类型的属性时,可以写作 lateinit var name: String。这里的关键点在于,属性必须是非空的(即类型后没有问号),且必须是可变的(使用 var 而非 val)。这是因为 lateinit 允许在属性被访问之前,由开发者在代码的任意位置手动赋值,从而实现了“延迟”初始化的效果。

这种机制特别适用于那些在构造函数中无法确定初始值,但在对象生命周期内的某个时刻必然会被赋值的场景。例如,在 Android 开发中,Activity 或 Fragment 的视图绑定对象往往在 onCreate 或 onViewCreated 方法中初始化,此时使用 lateinit 可以避免在属性声明处赋予默认值,同时也避免了使用可空类型带来的频繁空安全检查。需要注意的是,如果在属性被初始化之前尝试访问它,Kotlin 会抛出 UninitializedPropertyAccessException 异常,这是运行时错误,因此开发者必须确保在访问前已完成赋值操作。

2.2 核心特性与限制

lateinit 的核心特性在于其“显式控制”能力。与 by lazy 不同,lateinit 不会自动执行初始化逻辑,而是完全依赖开发者在代码中显式地赋予初始值。这意味着初始化时机完全由业务逻辑决定,灵活性极高。然而,这也带来了一些限制:首先,lateinit 只能用于 var 属性,不能用于 val 属性,因为 val 要求在声明时或构造函数中确定值,而 lateinit 允许后续修改。其次,lateinit 不能用于基本数据类型(如 Int、Double 等),只能用于引用类型。这是因为基本数据类型在内存中占用空间较小,且默认值明确,而 lateinit 的设计初衷是处理复杂的对象引用。

此外,lateinit 的属性在初始化前是不可见的,这意味着在调试过程中,如果属性未被赋值,IDE 可能会提示潜在的空指针风险。开发者需要谨慎管理初始化顺序,确保在访问属性之前,所有依赖的资源都已就绪。这种显式控制虽然增加了代码的复杂性,但也提供了更高的灵活性,适合那些初始化逻辑复杂、依赖外部资源或需要动态配置的场景。通过合理使用 lateinit,开发者可以在保持代码简洁性的同时,实现高效的资源管理和内存优化。

2.1 基本语法与限制

lateinit 是 Kotlin 中的关键字,专门用于修饰“可变非空属性”(即 var 修饰的非空引用类型),其语法格式非常简洁:在 var 属性前添加 lateinit 关键字,声明时无需赋值,后续在合适时机手动赋值即可。需要注意的是,lateinit 不能用于修饰 val 属性,也不能修饰基本数据类型(如 Int、Double、Boolean 等),只能修饰非空的引用类型(如 String、自定义类、控件对象等)。

// 语法格式示例
class DemoClass {
    // lateinit 修饰可变非空引用类型属性
    lateinit var userService: UserService
    lateinit var title: String
}

2.2 适用场景

lateinit 的核心特点是“显式初始化”,因此其适用场景集中在“初始化时机由外部逻辑控制,无法在属性声明时完成”的场景,常见的有以下两类:

  • 依赖注入场景:在使用 Spring、Hilt 等依赖注入框架时,服务类对象通常由框架负责初始化并注入到目标类中,开发者无法在属性声明时直接赋值。此时用 lateinit 修饰依赖对象,既保证了非空性,又留给框架后续注入的空间。
  • 生命周期回调初始化场景:在 Android 开发中,Activity、Fragment 等组件的生命周期由系统管理,控件初始化(如 findViewById、ViewBinding)通常需要在 onCreate、onViewCreated 等生命周期方法中完成,无法在属性声明时执行。这种情况下,用 lateinit 修饰控件对象是典型用法。

2.3 简单示例

示例 1:Android 中 Activity 控件初始化

在 Android 开发中,Activity 的 onCreate 方法是系统回调的初始化入口,控件必须在该方法中或之后初始化,此时用 lateinit 修饰控件属性非常合适。

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    // lateinit 修饰 TextView 控件,声明时不赋值
    lateinit var tvTitle: TextView

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 生命周期回调中手动初始化 lateinit 属性
        tvTitle = findViewById(R.id.tv_title)
        // 初始化后正常使用
        tvTitle.text = "延迟初始化演示"
    }
}

示例 2:依赖注入场景使用

在使用 Hilt 依赖注入框架时,Repository 层对象由框架注入,用 lateinit 修饰可避免空值声明。

import javax.inject.Inject

class UserViewModel {
    // lateinit 修饰 Repository 对象,由 Hilt 后续注入
    @Inject
    lateinit var userRepository: UserRepository

    // 业务方法中使用注入的 Repository
    fun getUserInfo(userId: String): User {
        return userRepository.getUserById(userId)
    }
}

// 被注入的 Repository 类
class UserRepository {
    fun getUserById(userId: String): User {
        // 模拟从网络或数据库获取用户信息
        return User(userId, "张三")
    }
}

data class User(val id: String, val name: String)

2.4 关键注意点

三、by lazy:惰性初始化(只读属性专用)

3.1 基本语法

by lazy 是 Kotlin 中的惰性初始化语法,通过“委托”机制实现,专门用于修饰“只读属性”(val 修饰的属性)。其语法格式为:val 属性名: 类型 by lazy { 初始化逻辑 }。其中,lazy 是一个高阶函数,接收一个 Lambda 表达式作为参数,该 Lambda 表达式就是属性的初始化逻辑;by 关键字表示将属性的初始化和访问逻辑委托给 lazy 函数返回的委托对象。

3.2 核心特性

by lazy 的核心优势在于“惰性”和“自动化”,其关键特性可总结为以下三点:

  • 首次访问才执行初始化:在属性被第一次读取之前,初始化代码块不会执行。这种机制能有效避免不必要的资源消耗,特别适用于耗时操作或复杂对象的创建场景。
  • 线程安全保证:默认情况下,by lazy 是线程安全的。在多线程环境中,无论多少个线程同时首次访问该属性,初始化逻辑块仅会被执行一次,确保初始化的原子性和一致性。
  • 结果缓存复用:一旦初始化完成,lazy 委托会将计算结果缓存起来。后续对该属性的所有访问将直接返回缓存的值,而不再重新执行初始化逻辑,从而显著提升性能。

3.3 适用场景

by lazy 的“惰性初始化”和“只读”特性,使其非常适合以下场景:

  • 初始化成本高的场景:如加载大型配置文件、创建数据库连接池、初始化复杂的第三方 SDK 客户端等,这些操作耗时耗资源,若在类初始化时就执行,会导致类加载缓慢。用 by lazy 修饰,可在真正需要使用时再初始化,提升类加载效率。
  • 按需加载场景:某些属性并非所有业务流程都会用到(如统计模块的报表生成器、特定功能的工具类),用 by lazy 修饰可实现“用则初始化,不用则不初始化”,避免资源浪费。
  • 工具类单例场景:在实现工具类单例时,可通过 by lazy 实现线程安全的惰性单例,简化单例代码的编写。

3.4 简单示例

示例 1:加载大型配置文件

配置文件加载通常需要读取磁盘文件并解析,成本较高,用 by lazy 实现按需加载非常合适。

import java.io.File
import java.util.Properties

// 应用配置类
data class AppConfig(
    val apiBaseUrl: String,
    val timeout: Int,
    val maxRetry: Int
)

class ConfigManager {
    // by lazy 修饰配置属性,首次访问时加载
    val appConfig: AppConfig by lazy {
        println("开始加载配置文件...")
        // 初始化逻辑:读取并解析配置文件
        val properties = Properties()
        File("config.properties").inputStream().use {
            properties.load(it)
        }
        // 构建配置对象并返回
        AppConfig(
            apiBaseUrl = properties.getProperty("api.base.url"),
            timeout = properties.getProperty("request.timeout").toInt(),
            maxRetry = properties.getProperty("request.max.retry").toInt()
        )
    }
}

fun main() {
    val configManager = ConfigManager()
    println("ConfigManager 已创建,但配置未加载")

    // 首次访问 appConfig,触发初始化
    val config = configManager.appConfig
    println("配置加载完成:$config")

    // 再次访问,直接返回已加载的配置
    val config2 = configManager.appConfig
    println("再次访问配置:$config2")
}

示例 2:实现线程安全的工具类单例

通过 by lazy 可简洁实现工具类的惰性单例,且默认线程安全。

// 字符串工具类单例
object StringUtils {
    // 惰性初始化复杂的字符串处理器(假设初始化成本高)
    val stringProcessor by lazy {
        println("初始化字符串处理器...")
        StringProcessor()
    }

    // 工具方法,使用惰性初始化的处理器
    fun formatString(text: String): String {
        return stringProcessor.format(text)
    }
}

// 复杂的字符串处理器(模拟初始化成本高)
class StringProcessor {
    fun format(text: String): String {
        return text.trim().replace("  ", " ")
    }
}

fun main() {
    println("调用工具方法前")
    // 首次调用工具方法,触发 stringProcessor 初始化
    val result1 = StringUtils.formatString("  hello  world  ")
    println("格式化结果1:$result1")

    // 再次调用,不触发初始化
    val result2 = StringUtils.formatString("  kotlin  lazy  ")
    println("格式化结果2:$result2")
}

四、lateinit 与 by lazy 核心差异对比

尽管两者都能实现延迟初始化,但在语义、约束和使用方式上存在显著差异。理解这些差异有助于在开发中选择最合适的方案。

4.1 初始化时机与触发条件

两者均属于延迟初始化机制,但触发逻辑略有不同。by lazy 严格遵循“首次访问即初始化”的原则,只要代码中读取该属性,Lazy 块中的逻辑就会立即执行。而 lateinit 则更为被动,它仅声明属性将在后续被赋值,只有当开发者显式调用 setter 方法或进行赋值操作时,初始化才会发生。如果属性从未被赋值,lateinit 属性将保持未初始化状态,访问时会抛出异常;而 by lazy 属性在首次访问后,其值将永久固定。

4.2 可变性与只读性约束

这是两者最本质的区别之一。by lazy 只能修饰 val 属性,这意味着初始化后的值是不可变的,保证了状态的一致性。这种设计适用于那些一旦确定就不再改变的资源或配置。相反,lateinit 必须修饰 var 属性,允许在初始化后再次修改其值。这使得 lateinit 适用于那些需要在对象生命周期内多次更新的状态变量,例如在异步回调中获取数据后更新 UI 组件。

4.3 线程安全机制

by lazy 默认提供线程安全保证,通过 synchronized 锁确保多线程环境下初始化逻辑只执行一次,适合并发场景。虽然这带来了一定的性能开销,但保证了数据的安全性。开发者可通过指定 LazyThreadSafetyMode.NONE 来移除锁,以换取单线程下的高性能。lateinit 本身不提供任何线程安全机制,它只是一个编译器指令,告知编译器跳过空值检查。因此,在多线程环境中使用 lateinit 时,开发者必须自行处理同步问题,否则可能导致竞态条件或初始化不一致。

4.4 错误处理与调试

当访问未初始化的 lateinit 属性时,Kotlin 会抛出 UninitializedPropertyAccessException 异常,明确指出属性未被赋值。这有助于在开发阶段快速定位遗漏初始化的代码。而 by lazy 在初始化过程中若 Lambda 表达式抛出异常,该异常会直接传递给调用者,且由于初始化逻辑只执行一次,后续访问不会重新尝试初始化,因此需要确保初始化逻辑的健壮性。

4.5 总结对比表

为了更直观地展示差异,以下是核心特性的对比总结:

  • 修饰符要求:by lazy 仅限 val;lateinit 仅限 var。
  • 初始化触发:by lazy 由首次读取触发;lateinit 由显式赋值触发。
  • 线程安全:by lazy 默认线程安全;lateinit 无内置线程安全。
  • 修改能力:by lazy 初始化后不可变;lateinit 初始化后可再次赋值。
  • 适用场景:by lazy 适合高成本、只读资源;lateinit 适合异步赋值、可变状态。

五、实用场景选型举例

理论差异需要结合实际场景才能更好地落地,下面通过具体业务场景,分析如何在 lateinit 和 by lazy 之间做出选择。

5.1 选 lateinit 场景

场景 1:Android Fragment 中 ViewBinding 初始化

Fragment 的 ViewBinding 初始化需要在 onViewCreated 方法中完成(此时视图已加载),初始化时机由系统生命周期控制,且 ViewBinding 对象可能需要在多个方法中使用,甚至在某些场景下需要重新绑定(如视图重建),因此适合用 lateinit 修饰。

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.demo.databinding.FragmentHomeBinding

class HomeFragment : Fragment() {
    // lateinit 修饰 ViewBinding 对象
    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup,
        savedInstanceState: Bundle
    ): View {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle) {
        super.onViewCreated(view, savedInstanceState)
        // 使用 lateinit 初始化后的 binding 对象
        binding.tvHomeTitle.text = "首页"
        binding.btnClick.setOnClickListener {
            // 业务逻辑
        }
    }

    // 视图销毁时可重置,后续重建时重新初始化
    override fun onDestroyView() {
        super.onDestroyView()
        // 若需释放资源,可在此处处理
    }
}

场景 2:Spring 依赖注入场景

在 Spring 开发中,Service 层对象由 Spring 容器管理,Controller 层通过 @Autowired 注解注入 Service 对象,初始化时机由 Spring 控制,开发者无法在声明时赋值,此时 lateinit 是理想选择。

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

@RestController
class UserController {
    // lateinit 修饰 Service 对象,由 Spring 注入
    @Autowired
    lateinit var userService: UserService

    @GetMapping("/user/{id}")
    fun getUser(@PathVariable id: String): User {
        // 使用注入的 Service 对象
        return userService.getUserById(id)
    }
}

interface UserService {
    fun getUserById(id: String): User
}

@org.springframework.stereotype.Service
class UserServiceImpl : UserService {
    override fun getUserById(id: String): User {
        return User(id, "李四")
    }
}

data class User(val id: String, val name: String)

5.2 选 by lazy 场景

场景 1:后端服务中加载数据库连接池

在构建后端服务时,数据库连接池的创建通常涉及复杂的配置读取、网络握手及资源分配,初始化成本较高。若每次请求都重新创建连接池,将极大浪费系统资源并增加响应延迟。使用 by lazy 修饰连接池属性,可确保仅在首次访问时才执行初始化逻辑,后续访问直接复用已初始化的实例。这种“按需加载”机制不仅降低了启动开销,还避免了因配置错误导致的早期崩溃风险,特别适合对启动速度敏感且连接池复用率高的场景。

场景 2:大型配置文件或资源文件的懒加载

当应用需要加载大型 JSON 配置文件、本地数据库或静态资源文件时,这些操作往往涉及 I/O 读写,耗时较长。若在应用启动阶段立即加载所有资源,可能导致启动缓慢甚至 ANR(应用无响应)。通过 by lazy 将这些资源属性声明为延迟初始化,可以在用户真正需要访问特定功能模块时才触发加载动作。例如,仅在用户进入“设置”页面时才加载配置数据,从而显著提升应用的首屏加载速度和用户体验。此外,由于 by lazy 默认是线程安全的(使用 synchronized 锁),在多线程环境下访问这些资源时,无需额外处理同步问题,简化了代码逻辑。

场景 3:计算密集型数据的缓存

某些业务数据需要通过复杂的算法计算得出,如用户画像标签、推荐列表等。若每次请求都重新计算,将消耗大量 CPU 资源。使用 by lazy 可以将计算结果缓存到属性中,首次计算后保存结果,后续访问直接返回缓存值。这种方式避免了重复计算,提升了系统性能。需要注意的是,如果数据源发生变化,需手动重置属性或重新初始化,以确保数据的时效性。对于静态且计算成本高的数据,by lazy 是理想的缓存策略。

数据库连接池的创建需要初始化多个连接,成本较高,且服务启动时不一定立即需要数据库操作,用 by lazy 修饰可实现“首次执行数据库操作时再创建连接池”,提升服务启动速度。

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import javax.sql.DataSource

class DataSourceManager {
    // by lazy 修饰连接池,首次使用时初始化
    val dataSource: DataSource by lazy {
        println("创建数据库连接池...")
        val config = HikariConfig()
        config.jdbcUrl = "jdbc:mysql://localhost:3306/test"
        config.username = "root"
        config.password = "123456"
        HikariDataSource(config)
    }

    // 执行数据库查询,首次调用时触发连接池初始化
    fun queryData(sql: String): List> {
        val connection = dataSource.connection
        // 执行查询逻辑...
        connection.close()
        return emptyList()
    }
}

fun main() {
    val dataSourceManager = DataSourceManager()
    println("DataSourceManager 已创建")
    // 首次调用 queryData,触发连接池初始化
    dataSourceManager.queryData("SELECT * FROM user")
    // 再次调用,使用已创建的连接池
    dataSourceManager.queryData("SELECT * FROM order")
}

场景 2:工具类中初始化复杂解析器

在 JSON 解析工具类中,若使用的解析器(如 Jackson 的 ObjectMapper)初始化成本较高,且工具类可能被加载但不执行解析操作,用 by lazy 修饰可实现按需初始化。

import com.fasterxml.jackson.databind.ObjectMapper

object JsonUtils {
    // by lazy 修饰 ObjectMapper,首次使用时初始化
    private val objectMapper by lazy {
        println("初始化 ObjectMapper...")
        ObjectMapper().apply {
            // 配置解析规则(如日期格式)
            dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        }
    }

    // 序列化方法,首次调用时触发 ObjectMapper 初始化
    fun toJson(obj: Any): String {
        return objectMapper.writeValueAsString(obj)
    }

    // 反序列化方法
    fun  fromJson(json: String, clazz: Class<T>): T {
        return objectMapper.readValue(json, clazz)
    }
}

fun main() {
    println("JsonUtils 已加载")
    // 首次调用 toJson,触发 ObjectMapper 初始化
    val user = User("1", "王五")
    val json = JsonUtils.toJson(user)
    println("序列化结果:$json")

    // 再次调用,使用已初始化的 ObjectMapper
    val user2 = JsonUtils.fromJson(json, User::class.java)
    println("反序列化结果:$user2")
}

data class User(val id: String, val name: String)

六、总结与避坑建议

6.1 核心知识点回顾

本文围绕 lateinit 与 by lazy 两大延迟初始化方案展开,核心知识点可归纳为以下三点:

  • 基础定义:二者均为 Kotlin 延迟初始化方案,lateinit 是显式延迟初始化关键字,修饰 var 非空引用类型;by lazy 是惰性初始化委托,修饰 val 任意类型。
  • 核心特性:lateinit 需手动触发初始化,支持重复赋值,无默认线程安全;by lazy 首次访问自动初始化,仅初始化一次,默认线程安全。
  • 核心差异:最关键的差异在属性可变性(var vs val)和初始化时机(手动 vs 自动),其他差异均围绕这两点衍生。

6.2 避坑点

在实际使用中,以下坑点容易引发问题,需重点规避:

6.3 选型技巧

结合前文的差异分析和场景示例,可总结出一套简单高效的选型流程,按以下步骤即可快速确定方案:

  1. 第一步:确定属性可变性:先判断属性是否需要修改。若需要修改(用 var 修饰),直接选择 lateinit;若不需要修改(用 val 修饰),进入下一步。
  2. 第二步:确定初始化时机:若属性初始化时机由外部逻辑控制(如依赖注入、生命周期回调),选择 lateinit;若属性可在首次访问时自动初始化,且初始化成本较高或按需加载,选择 by lazy。
  3. 第三步:结合数据类型和线程安全:若为基本类型,直接排除 lateinit;若为多线程场景,by lazy 更省心(默认线程安全);若为单线程场景,by lazy 可指定 NONE 模式提升性能。

常见陷阱与注意事项

在实际开发中,除了选型逻辑,还需警惕以下常见的使用陷阱,以避免运行时异常或编译错误:

  • lateinit 未初始化访问:这是最常见的坑点,若在手动赋值前访问 lateinit 属性,会抛出 UninitializedPropertyAccessException。解决办法:通过 ::属性名.isInitialized 检查初始化状态,或确保在使用前必然完成初始化(如在生命周期回调中赋值)。
  • lateinit 修饰基本类型或 val 属性:lateinit 仅支持 var 非空引用类型,若修饰 Int、val 等,编译直接报错。若需延迟初始化基本类型,改用 var 类型 by Delegates.notNull()
  • by lazy 修饰 var 属性:by lazy 基于 val 属性的只读特性实现,修饰 var 属性会编译报错,需牢记“by lazy 只配 val”。
  • 多线程场景忽视 by lazy 线程安全模式:在单线程场景下使用默认的线程安全模式,会因加锁带来不必要的性能开销;在多线程场景下随意使用 NONE 模式,会导致初始化多次。解决办法:根据线程环境选择合适的 LazyThreadSafetyMode。

选型口诀总结

最后用一句口诀总结选型逻辑:“可变属性选 lateinit,手动控制初始化;只读属性选 by lazy,自动惰性更省心”。掌握这句口诀,结合实际场景灵活调整,就能高效正确地使用两种延迟初始化方案。

class TestClass {
    lateinit var message: String

    fun printMessage() {
        // 先检查是否初始化,再使用,避免异常
        if (::message.isInitialized) {
            println(message)
        } else {
            println("message 尚未初始化")
        }
    }
}

fun main() {
    val test = TestClass()
    test.printMessage() // 输出:message 尚未初始化
    test.message = "Hello lateinit"
    test.printMessage() // 输出:Hello lateinit
    test.message = "重新赋值" // 可重复赋值
    test.printMessage() // 输出:重新赋值
}

// 语法格式示例
class DemoClass {
    // val 属性通过 by lazy 委托实现惰性初始化
    val config: AppConfig by lazy {
        // 初始化逻辑:加载配置文件并解析
        loadConfigFromFile("config.properties")
    }
}

// 线程安全模式指定示例
class SingleThreadDemo {
    // 单线程场景,取消线程安全锁提升性能
    val data: List by lazy(LazyThreadSafetyMode.NONE) {
        loadLargeData() // 单线程下无需加锁
    }

    private fun loadLargeData(): List {
        println("执行初始化逻辑")
        return listOf("数据1", "数据2", "数据3")
    }
}

fun main() {
    val demo = SingleThreadDemo()
    println("首次访问:")
    demo.data.forEach { println(it) } // 首次访问,执行初始化
    println("再次访问:")
    demo.data.forEach { println(it) } // 再次访问,不执行初始化
}

对比维度lateinitby lazy
修饰属性类型仅支持 var(可变属性)仅支持 val(只读属性)
初始化时机显式手动触发(如生命周期回调、依赖注入时)自动触发(属性首次被访问时)
支持数据类型仅支持非空引用类型(如 String、自定义类),不支持基本类型支持任意类型(引用类型、基本类型均可)
线程安全性无默认线程安全保障,需开发者手动处理并发问题默认线程安全(加锁),可通过指定模式取消
初始化次数可多次赋值(初始化后仍可修改)仅一次初始化(初始化后不可修改,返回固定结果)
初始化逻辑位置初始化逻辑分散在手动赋值的位置(如方法中)初始化逻辑集中在 lazy 函数的 Lambda 中,与属性声明绑定
var age: Int by Delegates.notNull() ::属性名.isInitialized

《夸克》非常好用的免费AI浏览器

下载APP查看

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多