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

相关推荐
彭于晏6892 小时前
Android高级控件
android·java·android-studio
666xiaoniuzi7 小时前
深入理解 C 语言中的内存操作函数:memcpy、memmove、memset 和 memcmp
android·c语言·数据库
沐言人生12 小时前
Android10 Framework—Init进程-8.服务端属性文件创建和mmap映射
android
沐言人生12 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
沐言人生12 小时前
Android10 Framework—Init进程-7.服务端属性安全上下文序列化
android·android studio·android jetpack
追光天使12 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru13 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数14 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数14 小时前
Android车载——VehicleHal运行流程(Android 11)
android