Kotlin设计模式之延迟初始化

Lazy vs Lateinit vs Nullable

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

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

1、什么是Lazy模式

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

2、通过Lazy委托

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

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

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

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)
}

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

LazyExample init
LazyVal init
1
1

2.1、线程安全

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

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

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元素。

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变量(编译器不会报错)。但这会抛出异常。

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”状态。

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委托。优点是内置线程安全。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值