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

相关推荐
工程师老罗5 小时前
如何在Android工程中配置NDK版本
android
Libraeking9 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack
市场部需要一个软件开发岗位9 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
JMchen12311 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
crmscs12 小时前
剪映永久解锁版/电脑版永久会员VIP/安卓SVIP手机永久版下载
android·智能手机·电脑
localbob12 小时前
杀戮尖塔 v6 MOD整合版(Slay the Spire)安卓+PC端免安装中文版分享 卡牌肉鸽神作!杀戮尖塔中文版,电脑和手机都能玩!杀戮尖塔.exe 杀戮尖塔.apk
android·杀戮尖塔apk·杀戮尖塔exe·游戏分享
机建狂魔12 小时前
手机秒变电影机:Blackmagic Camera + LUT滤镜包的专业级视频解决方案
android·拍照·摄影·lut滤镜·拍摄·摄像·录像
hudawei99612 小时前
flutter和Android动画的对比
android·flutter·动画
lxysbly14 小时前
md模拟器安卓版带金手指2026
android
儿歌八万首14 小时前
硬核春节:用 Compose 打造“赛博鞭炮”
android·kotlin·compose·春节