百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Kotlin 中的延迟初始化(kotlin synchronized)

lipiwang 2024-11-15 22:02 21 浏览 0 评论

1. 概述

在本文中,我们将研究 Kotlin 语法中最有趣的特性之一——延迟初始化。

我们还将研究lateinit关键字,它允许我们欺骗编译器并在类的主体中初始化非空字段——而不是在构造函数中。

2. Java 中的延迟初始化模式

有时我们需要构造具有繁琐初始化过程的对象。此外,我们通常无法确定在我们的程序开始时我们支付了初始化成本的对象是否会在我们的程序中使用。

“延迟初始化”的概念旨在防止对对象进行不必要的初始化。在 Java 中,以惰性和线程安全的方式创建对象并不是一件容易的事情。像Singleton这样的模式在多线程、测试等方面存在重大缺陷——它们现在被广泛称为需要避免的反模式。

或者,我们可以利用 Java 中内部对象的静态初始化来实现惰性:

public class ClassWithHeavyInitialization {
 
    private ClassWithHeavyInitialization() {
    }

    private static class LazyHolder {
        public static final ClassWithHeavyInitialization INSTANCE = new ClassWithHeavyInitialization();
    }

    public static ClassWithHeavyInitialization getInstance() {
        return LazyHolder.INSTANCE;
    }
}

请注意,只有当我们在ClassWithHeavyInitialization上调用getInstance()方法时,才会加载静态LazyHolder类,并创建ClassWithHeavyInitialization的新实例。接下来,实例将被分配给静态最终INSTANCE引用。

我们可以测试getInstance()每次被调用时都返回相同的实例:

@Test
public void giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall() {
    // when
    ClassWithHeavyInitialization classWithHeavyInitialization 
      = ClassWithHeavyInitialization.getInstance();
    ClassWithHeavyInitialization classWithHeavyInitialization2 
      = ClassWithHeavyInitialization.getInstance();

    // then
    assertTrue(classWithHeavyInitialization == classWithHeavyInitialization2);
}

这在技术上是可以的,但对于这样一个简单的概念来说当然有点太复杂了


3. Kotlin 中的延迟初始化

我们可以看到,在 Java 中使用惰性初始化模式是相当麻烦的。我们需要编写大量样板代码来实现我们的目标。幸运的是,Kotlin 语言内置了对延迟初始化的支持

要创建将在第一次访问时初始化的对象,我们可以使用惰性方法:

@Test
fun givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce() {
    // given
    val numberOfInitializations: AtomicInteger = AtomicInteger()
    val lazyValue: ClassWithHeavyInitialization by lazy {
        numberOfInitializations.incrementAndGet()
        ClassWithHeavyInitialization()
    }
    // when
    println(lazyValue)
    println(lazyValue)

    // then
    assertEquals(numberOfInitializations.get(), 1)
}

正如我们所见,传递给惰性函数的 lambda只执行了一次。

当我们第一次访问lazyValue时 - 发生了实际的初始化,并且ClassWithHeavyInitialization类的返回实例被分配给了lazyValue引用。随后对lazyValue 的访问返回了先前初始化的对象。

我们可以将LazyThreadSafetyMode作为参数传递给惰性函数。默认发布模式是SYNCHRONIZED,这意味着只有一个线程可以初始化给定的对象。

我们可以传递一个PUBLICATION作为一种模式——这将导致每个线程都可以初始化给定的属性。分配给引用的对象将是第一个返回值——因此第一个线程获胜。

让我们来看看那个场景:

@Test
fun whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce() {
 
    // given
    val numberOfInitializations: AtomicInteger = AtomicInteger()
    val lazyValue: ClassWithHeavyInitialization
      by lazy(LazyThreadSafetyMode.PUBLICATION) {
        numberOfInitializations.incrementAndGet()
        ClassWithHeavyInitialization()
    }
    val executorService = Executors.newFixedThreadPool(2)
    val countDownLatch = CountDownLatch(1)
 
    // when
    executorService.submit { countDownLatch.await(); println(lazyValue) }
    executorService.submit { countDownLatch.await(); println(lazyValue) }
    countDownLatch.countDown()

    // then
    executorService.awaitTermination(1, TimeUnit.SECONDS)
    executorService.shutdown()
    assertEquals(numberOfInitializations.get(), 2)
}

我们可以看到同时启动两个线程会导致ClassWithHeavyInitialization的初始化发生两次。

还有第三种模式——NONE——但它不应该在多线程环境中使用,因为它的行为是未定义的。

4. Kotlin 的lateinit

在 Kotlin 中,在类中声明的每个不可为 null 的类属性都应该在构造函数中或作为变量声明的一部分进行初始化。如果我们不这样做,那么 Kotlin 编译器会抱怨一条错误消息:

Kotlin: Property must be initialized or be abstract

这基本上意味着我们应该初始化变量或将其标记为 abstract

另一方面,在某些情况下,可以通过例如依赖注入动态分配变量。

为了推迟变量的初始化,我们可以指定一个字段是lateinit。我们通知编译器 this will 变量将在稍后分配,我们将编译器从确保此变量初始化的责任中解放出来:

lateinit var a: String
 
@Test
fun givenLateInitProperty_whenAccessItAfterInit_thenPass() {
    // when
    a = "it"
    println(a)

    // then not throw
}

如果我们忘了初始化lateinit属性,我们会得到一个UninitializedPropertyAccessException

@Test(expected = UninitializedPropertyAccessException::class)
fun givenLateInitProperty_whenAccessItWithoutInit_thenThrow() {
    // when
    println(a)
}

值得一提的是,我们只能使用 具有非原始数据类型的lateinit变量。因此,不可能写出这样的东西:

lateinit var value: Int

如果我们这样做,我们会得到一个编译错误:

Kotlin: 'lateinit' modifier is not allowed on properties of primitive types

5. 结论

在这个快速教程中,我们研究了对象的延迟初始化。

首先,我们看到了如何在 Java 中创建线程安全的延迟初始化。我们看到它非常繁琐,需要大量的样板代码。

接下来,我们深入研究了用于延迟初始化属性的 Kotlin惰性关键字。最后,我们看到了如何使用lateinit关键字延迟分配变量。

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: