Kotlin设计模式之延迟初始化

Lazy vs Lateinit vs Nullable

在本博客中,我们将介绍Kotlin提供的不同选项,以实现延迟初始化模式。我们将指出如何使用它们以及选择哪些。

Kotlin提供了三种内置方法来实现此模式:

1、什么是Lazy模式 Lazy初始化模式,也称为延迟初始化,用于将对象的创建推迟到到稍后的时间点。在大多数情况下,成员变量是在创建其父对象时初始化的。然而,在某些情况下,将创建推迟到稍后时间是有利的。例如,如果创建对象需要花费大量时间,并且将其推迟到使用它的实际时间点是有意义的,则可能会发生这种情况。另一个原因可能是在创建父对象时我们无法访问具体对象。例如,一个Android应用程序的Activity类是一个例子。

2、通过Lazy委托

Kotlin提供了一个预构建属性委托,可以包装任何对象或成员变量。如果您不确定如何在Kotlin中使用委托,我下一篇文章会介绍。

使用此方法的缺点是,无法重新分配此委托包装的成员。问题是Lazy没有实现该setValue功能。val 它只能在第一次分配期间用于只读。即使您将确实的函数实现为扩展函数,缓存的值也是类型val。

以下代码显示了一个简单的用例。是lazyVal在第一次访问时分配的。

kotlin 复制代码
class LazyExample {
    val lazyVal: Int by lazy {
        println("LazyVal init")
        1
    }
    
    init {
        println("LazyExample init")
    }
}

fun main() {
    val lazy = LazyExample()
    println(lazy.lazyVal)
    println(lazy.lazyVal)
}

该代码的输出如下。正如您所看到的,委托的函数体仅评估一次。然后它使用缓存的值。

csharp 复制代码
LazyExample init
LazyVal init
1
1

2.1、线程安全

要创建新的Lazy对象,您必须使用特定的初始化函数initalizer。默认情况下,该函数是线程安全的。请注意,返回的实例使用自身进行同步。如果尝试从外部代码同步以同步包装的成员变量,可能会导致死锁。

以下代码取自原生Kotlin API。它显示了创建线程(不)安全代码的不同选项。您还可以看到它可能会抛出异常。我们建议使用标准模式(线程安全)以避免任何冲突。

kotlin 复制代码
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = 
    when(mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> if (isExperimentalMM()) SynchronizedLazyImpl(initializer) else throw UnsuppoetedOperationException()
        LazyThreadSafetyMode.PUBLICATION -> if (isExperimentalMM()) SafePublicationLazyImpl(initializer) else FreezeAwareLazyImpl(initializer)
        LazyThreadSafeMode.NONE -> UnsafeLazyImpl(iniitializer)
    }

2.2、优点和缺点

优点:

  • 现成安全
  • 无需检查是否已初始化

缺点:

  • 只读(标准实现)

3、Lateinit关键字

lateinit关键字不能用于基本类型。这意味着为了使用它,你必须有一个适当的类。实际上,这不是问题,因为基本类型不太可能用延迟初始化。此外,它只能用于可变类型。换句话说,对于变量(var)。

3.1、Android示例

这种方法经常在Android类中Activity中使用。通常,在此类中,您提供对XML文件中定义的UI元素的绑定。但你只能在Activity里特殊的方法onCreate()之后才能访问这些对象。因此,您被迫延迟分配按钮等,直到您可以访问UI元素。

kotlin 复制代码
class MainActivity: AppCompatActivity() {
    private lateinit var button: Button
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        button = findViewById(R.id.myButton)
    }
}

3.2、检查是否初始化

这个关键字的一个大问题是,你的代码中有一个潜在的异常。您可以在实际分配变量之前访问该lateinit变量(编译器不会报错)。但这会抛出异常。

kotlin 复制代码
class LateInitExamplt {
    lateinit var lateInit: NotPrimitive
    
    fun isInit(): Boolean {
        return this::lateInit.isInitialized
    }
}

fun main() {
    var obj = LateInitExample()
    if (obj.isInit()) {
        println(obj.lateInit)
    }
}

3.3、线程安全

这种方法不是线程安全的。您必须确保在多线程的情况下正确处理初始化。

3.4、优点和缺点

优点:

  • 易于使用(无开销)
  • 适用于var和val

缺点:

  • 线程不安全
  • 需要检查(确保)它已初始化
  • 不适用于原始类型

4、Nullable对象

默认情况下,Kotlin中的每个(成员)变量都必须为非空。然而这个约束可以被削弱来实现nullable。从某种意义上说,如果我们能消除这个现实,Kotlin的行为就会更像Java代码。

以下示例延时如何使用可为null的对象。通过使用"?"对象上的修饰符被声明为可为空。它可以具有"null"状态。

kotlin 复制代码
class NullableObject {
    var nullable: Int? = null
}

fun main() {
    var obj = NullableObject()
    obj.nullable = 2
    if (obj.nullable != null) {
        println(obj.nullable!!)
    }
}

4.1、线程安全

默认情况下,这种方法不是线程安全的。在多线程环境中使用它时,你必须确保它已正确初始化。

4.2、检查是否不为空

该方法有一个大问题是存在出现NullPointerException的潜在风险。因此,您需要检查对象是否为空(参见上面代码)。Kotlin以一种方式帮助您,您需要显示指示该变量是否可以安全使用。这个检查是在编译时完成,只是一个让你思考的帮助器(使用!!)。

4.3、优点和缺点

优点:

  • 适用于var和val
  • 适用于各种类型
  • 变量的额外状态

缺点:

  • 线程不安全
  • 存在潜在的异常
  • 修改编写代码(?!!

5、总结

我们看到所有方法都有优点和缺点。

我们不建议使用nullable类型。Kotlin的varval关键字可以避免使用异常,这是一种非常好的做法。如果移除这个约束,可能会导致代码的可读性和可维护性下降。

我们建议lateinit在初始化父对象时无法访问该对象的用例中使用关键字(如Activity里面View控件的初始化)

对于所有其他用例,我们建议使用Lazy委托。优点是内置线程安全。

相关推荐
千里马学框架11 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台11 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone11 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc12 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo14 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770014 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work15 小时前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼15 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work15 小时前
ch21 签名、校验与发版
kotlin
AFinalStone16 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui