Kotlin 知识点二 延迟初始化和密封类
对变量延迟初始化
Kotlin 语言的许多特性,包括变量不可变,变量不可为空,等等。这些特性
都是为了尽可能地保证程序安全而设计的,但是有些时候这些特性也会在编码时给我们带来不
少的麻烦。
比如,如果你的类中存在很多全局变量实例,为了保证它们能够满足Kotlin 的空指针检查语法标
准,你不得不做许多的非空判断保护才行,即使你非常确定它们不会为空。
下面我们通过一个具体的例子来看一下吧,就使用刚刚的UIBestP ractice 项目来作为例子。如
果你仔细观察MainActivity 中的代码,会发现这里适配器的写法略微有点特殊:
class MainActivity : AppCompatActivity(), View.OnClickListener {private var adapter: MsgAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { ... adapter = MsgAdapter(msgList) ... } override fun onClick(v: View?) { ... adapter?.notifyItemInserted(msgList.size - 1) ... }
}
这里我们将adapter设置为了全局变量,但是它的初始化工作是在onCreate()方法中进行
的,因此不得不先将adapter赋值为null,同时把它的类型声明成MsgAdapter?。
虽然我们会在onCreate()方法中对adapter进行初始化,同时能确保onClick()方法必然在
onCreate()方法之后才会调用,但是我们在onClick()方法中调用adapter的任何方法时仍
然要进行判空处理才行,否则编译肯定无法通过。
而当你的代码中有了越来越多的全局变量实例时,这个问题就会变得越来越明显,到时候你可
能必须编写大量额外的判空处理代码,只是为了满足Kotlin 编译器的要求。
幸运的是,这个问题其实是有解决办法的,而且非常简单,那就是对全局变量进行延迟初始
化。
延迟初始化使用的是lateinit关键字,它可以告诉Kotlin 编译器,我会在晚些时候对这个变量
进行初始化,这样就不用在一开始的时候将它赋值为null了。
接下来我们就使用延迟初始化的方式对上述代码进行优化,如下所示:
class MainActivity : AppCompatActivity(), View.OnClickListener {private lateinit var adapter: MsgAdapter override fun onCreate(savedInstanceState: Bundle?) { ... adapter = MsgAdapter(msgList) ... } override fun onClick(v: View?) { ... adapter.notifyItemInserted(msgList.size - 1) ... }
}
可以看到,我们在adapter变量的前面加上了lateinit关键字,这样就不用在一开始的时候
将它赋值为null,同时类型声明也就可以改成MsgAdapter了。由于MsgAdapter是不可为空
的类型,所以我们在onClick()方法中也就不再需要进行判空处理,直接调用adapter的任何
方法就可以了。
当然,使用lateinit关键字也不是没有任何风险,如果我们在adapter变量还没有初始化的
情况下就直接使用它,那么程序就一定会崩溃,并且抛出一个
UninitializedP ropertyA ccessEx ception 异常,如图

当对一个全局变量使用了lateinit关键字时,请一定要确保它在被任何地方调用之前
已经完成了初始化工作,否则Kotlin 将无法保证程序的安全性。
另外,我们还可以通过代码来判断一个全局变量是否已经完成了初始化,这样在某些时候能够
有效地避免重复对某一个变量进行初始化操作,示例代码如下:
class MainActivity : AppCompatActivity(), View.OnClickListener {private lateinit var adapter: MsgAdapter override fun onCreate(savedInstanceState: Bundle?) { ... if (!::adapter.isInitialized) { adapter = MsgAdapter(msgList) } ... }
具体语法就是这样,::adapter.isInitialized可用于判断adapter变量是否已经初始
化。虽然语法看上去有点奇怪,但这是固定的写法。然后我们再对结果进行取反,如果还没有
初始化,那么就立即对adapter变量进行初始化,否则什么都不用做。
使用密封类优化代码
由于密封类通常可以结合RecyclerV iew 适配器中的ViewHolder 一起使用,因此我们就正好借
这个机会在本节学习一下它的用法。当然,密封类的使用场景远不止于此,它可以在很多时候
帮助你写出更加规范和安全的代码,所以非常值得一学。
首先来了解一下密封类具体的作用,这里我们来看一个简单的例子。新建一个Kotlin 文件,文件
名就叫Result.kt 好了,然后在这个文件中编写如下代码:
interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result
这里定义了一个Result接口,用于表示某个操作的执行结果,接口中不用编写任何内容。然后
定义了两个类去实现Result接口:一个Success类用于表示成功时的结果,一个Failure类
用于表示失败时的结果,这样就把准备工作做好了。
接下来再定义一个getResultMsg()方法,用于获取最终执行结果的信息,代码如下所示:
fun getResultMsg(result: Result) = when (result) { is Success -> result.msg is Failure -> result.error.message else -> throw IllegalArgumentException()
}
getResultMsg()方法中接收一个Result参数。我们通过when语句来判断:如果Result属
于Success,那么就返回成功的消息;如果Result属于Failure,那么就返回错误信息。到
目前为止,代码都是没有问题的,但比较让人讨厌的是,接下来我们不得不再编写一个else条
件,否则Kotlin 编译器会认为这里缺少条件分支,代码将无法编译通过。但实际上Result的执
行结果只可能是Success或者Failure,这个else条件是永远走不到的,所以我们在这里直接
抛出了一个异常,只是为了满足Kotlin 编译器的语法检查而已。
另外,编写else条件还有一个潜在的风险。如果我们现在新增了一个Unknown类并实现
Result接口,用于表示未知的执行结果,但是忘记在getResultMsg()方法中添加相应的条
件分支,编译器在这种情况下是不会提醒我们的,而是会在运行的时候进入else条件里面,从
而抛出异常并导致程序崩溃。
当然,这种为了满足编译器的要求而编写无用条件分支的情况不仅在Kotlin 当中存在,在Java 或
者是其他编程语言当中也普遍存在。
不过好消息是,Kotlin 的密封类可以很好地解决这个问题,下面我们就来学习一下。
密封类的关键字是sealed class,它的用法同样非常简单,我们可以轻松地将Result接口改
造成密封类的写法:
sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()
可以看到,代码并没有什么太大的变化,只是将interface关键字改成了sealed class。另
外,由于密封类是一个可继承的类,因此在继承它的时候需要在后面加上一对括号
那么改成密封类之后有什么好处呢?你会发现现在getResultMsg()方法中的else条件已经不
再需要了,如下所示:
fun getResultMsg(result: Result) = when (result) { is Success -> result.msg is Failure -> "Error is ${result.error.message}"
}
为什么这里去掉了else条件仍然能编译通过呢?这是因为当在when语句中传入一个密封类变量
作为条件时,Kotlin 编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类所对应
的条件全部处理。这样就可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的
情况。而如果我们现在新增一个Unknown类,并也让它继承自Result,此时
getResultMsg()方法就一定会报错,必须增加一个Unknown的条件分支才能让代码编译通
过。
这就是密封类主要的作用和使用方法了。另外再多说一句,密封类及其所有子类只能定义在同
一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的。
了解了这么多关于密封类的知识,接下来我们看一下它该如何结合MsgAdapter中的
ViewHolder 一起使用,并顺便优化一下MsgAdapter中的代码。
观看MsgAdapter现在的代码,你会发现onBindViewHolder()方法中就存在一个没有实际作
用的else条件,只是抛出了一个异常而已。对于这部分代码,我们就可以借助密封类的特性来
进行优化。首先删除MsgAdapter 中的Lef tViewHolder 和RightViewHolder ,然后新建一个
MsgViewHolder .kt 文件,在其中加入如下代码:
sealed class MsgViewHolder(view: View) : RecyclerView.ViewHolder(view)class LeftViewHolder(view: View) : MsgViewHolder(view) { val leftMsg: TextView = view.findViewById(R.id.leftMsg)
} class RightViewHolder(view: View) : MsgViewHolder(view) { val rightMsg: TextView = view.findViewById(R.id.rightMsg)
}
这里我们定义了一个密封类MsgViewHolder,并让它继承自RecyclerView.ViewHolder,
然后让LeftViewHolder和RightViewHolder继承自MsgViewHolder。这样就相当于密封
类MsgViewHolder只有两个已知子类,因此在when语句中只要处理这两种情况的条件分支即
可。
现在修改MsgAdapter中的代码,如下所示:
class MsgAdapter(val msgList: List<Msg>) : RecyclerView.Adapter<MsgViewHolder>() {... override fun onBindViewHolder(holder: MsgViewHolder, position: Int) {val msg = msgList[position] when (holder) { is LeftViewHolder -> holder.leftMsg.text = msg.contentis RightViewHolder -> holder.rightMsg.text = msg.content} } ...
}
这里我们将RecyclerView.Adapter的泛型指定成刚刚定义的密封类MsgViewHolder,这样
onBindViewHolder()方法传入的参数就变成了MsgViewHolder。然后我们只要在when语
句当中处理LeftViewHolder和RightViewHolder这两种情况就可以了,那个讨厌的else终
于不再需要了,这种RecyclerV iew 适配器的写法更加规范也更加推荐。
相关文章:
Kotlin 知识点二 延迟初始化和密封类
对变量延迟初始化 Kotlin 语言的许多特性,包括变量不可变,变量不可为空,等等。这些特性 都是为了尽可能地保证程序安全而设计的,但是有些时候这些特性也会在编码时给我们带来不 少的麻烦。 比如,如果你的类中存在很多…...
基于SpringBoot的“古城景区管理系统”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“古城景区管理系统”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 系统首页界面 系统注册界面 景…...
QT C++ QtConcurrent::run 异步任务 简单例子
QtConcurrent命名空间提供了高级API,使得无需使用低级线程原语即可编写多线程程序。 QtConcurrent::run是Qt框架中用于简化并发编程的一个功能,主要用于在后台线程中异步执行函数或成员函数。其主要用途包括: 异步执行函数…...
力扣hot100 —— 电话号码字母组合; 子集 (非回溯做法)简单易懂
由于博主对回溯也不是很熟悉,这里提出一种简单易懂的解法(有点暴力) 解题思路: 每个数字对应有自己的字母串; 首先遍历将每个字母存入也就是 res{{a},{b},{c}} 然后遍历后续数子对应的字母,让每个字母与…...
企业业务安全进阶之路:AI技术与数据分析的应用
在数字化时代,企业业务安全面临着前所未有的挑战。从网络安全到数据保护,每一个环节都至关重要。本文将探讨如何通过AI技术和数据分析来提升企业业务安全防护能力,确保企业在不断变化的安全威胁面前保持领先地位。 企业业务安全的重要性 企…...
windows本地升级npm
## 在管理员身份的cmd中: 运行以下命令来安装npm-windows-upgrade工具: npm install --global --production npm-windows-upgrade运行以下命令来升级到最新版本的npm: npm-windows-upgrade --npm-version latest如果你想升级到特定版本的n…...
【Redis】在Java中以及Spring环境下操作Redis
Java环境下: 1.创建maven 项目 2.导入依赖 <!-- redis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.2</version></dependency> 此处使用的是Jedis&…...
Sqlserver安全篇之_隐藏实例功能和禁用SQL Server Browser服务
总结: 1、隐藏实例功能和禁用SQL Server Browser服务的功能一样,对应非默认实例(且这个默认实例是1433端口)的情况下,都是需要在连接字符串中提供端口号才能连接到实例 2、隐藏实例功能后,就算开启了SQL Server Browser服务&#…...
Directx上传堆和默认堆注意事项
前景 之前我用directx写上传堆上传给默认堆Index,但是我发现数据无法被GPU读取 void HelloTriangle::createDefaultBuffer(const void* data, const UINT byteSize, ComPtr<ID3D12Resource>& defaultBuffer) {ThrowIfFiled(m_Device->CreateCommitte…...
Java封装弱密码校验工具类
弱密码校验工具类 通过检查密码是否符合某些安全策略来判断其强度 ● 密码长度 ● 字符类型 ● 常见密码组合 import java.util.Arrays; import java.util.HashSet; import java.util.Set;public class WeakPasswordCheckUtil {// 常见弱密码列表(可根据需求扩展&…...
Figure自研模型Helix发布,人形机器人迈向新纪元?
Figure 公司自 2022 年成立以来,便在人形机器人领域崭露头角,成为行业内备受瞩目的新星。公司由连续创业者 Brett Adcock 创立,总部位于美国加利福尼亚州桑尼维尔,汇聚了来自波士顿动力公司、特斯拉、谷歌 DeepMind 等知名企业的顶…...
vue3.0将后端返回的word文件流转换为pdf并导出+html2pdf.js将页面导出为pdf
实现思路 1.将Word文档转换为HTML:mammoth.js,它可以将.docx文件转换为HTML 2.将HTML转换为PDF:使用html2pdf.js将HTML转换为PDF 如果想要相同的效果,也可以把前端页面直接导出转换为pdf: 运用的插件:html2pdf.js 后端…...
(Arrow)试时间处理变得更简单
前言 Arrow库并不是简单的二次开发,而是在datetime的基础上进行了扩展和增强。它通过提供更简洁的API、强大的时区支持、丰富的格式化和解析功能以及人性化的显示,填补了datetime在某些功能上的空白。如果你需要更高效、更人性化的日期时间处理方式,Arrow库是一个不错的选择…...
SV基础(一):System Verilog与Verilog核心区别详解
文章目录 **1. 设计增强功能****数据类型扩展****接口(Interface)****2. 验证功能增强****断言(Assertions)****约束随机测试****功能覆盖率****3. 面向对象编程(OOP)****4. 测试平台(Testbench)改进****5. 语法简化****6. 其他关键区别****学习建议**System Verilog 是…...
锂电池使用和存储电压
表格补充说明: 每列数据中,2S和3S电池的数值都是单电芯数值的2倍和3倍;对于其他电压的电池,将单电芯数值乘以相应S数即可;理论上单个电芯过放电压为3.0V,实际中为了保险,电压降到3.6V即需充电。…...
【docker】namespace底层机制
Linux 的 Namespace 机制是实现容器化(如 Docker、LXC 等)的核心技术之一,它通过隔离系统资源(如进程、网络、文件系统等)为进程提供独立的运行环境。其底层机制涉及内核数据结构、系统调用和进程管理。以下是其核心实…...
欧拉回路与哈密尔顿回路: Fleury算法与Hierholzer 算法(C++)
图论中的回路是指一个路径, 它从某个顶点开始, 经过所有边恰好一次, 并回到起始顶点. 定义 欧拉回路: 从一个顶点出发, 经过每条边恰好一次, 并且最终回到起始顶点. 哈密尔顿回路: 从一个顶点出发, 经过每个顶点恰好一次, 并且最终回到起始顶点. 欧拉路径: 从一个顶点出发, …...
20250221 NLP
1.向量和嵌入 https://zhuanlan.zhihu.com/p/634237861 encoder的输入就是向量,提前嵌入为向量 二.多模态文本嵌入向量过程 1.文本预处理 文本tokenizer之前需要预处理吗? 是的,文本tokenizer之前通常需要对文本进行预处理。预处理步骤可…...
https:原理
目录 1.数据的加密 1.1对称加密 1.2非对称加密 2.数据指纹 2.1数据指纹实际的应用 3.数据加密的方式 3.1只使用对称加密 3.2只使用非对称加密 3.3双方都使用对称加密 3.4非对称加密和对称加密一起使用 4.中间人攻击 5.CA证书 5.1什么是CA证书 CA证书的验证 6.https的原理 1.数据…...
cmake命令记录
1.project(HELLO) project命令用于设置工程的名称,括号里的参数HELLO便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上。 2.add_executable(hello ./main.c) add_executable用于生成一个可执行文件,第一个参数代表生成的可…...
【Python模块】——pymysql
pymysql是python操作mysql的标准库,可以通过pip install快速导入pymysql包操作数据库 使用pymysql操作mysql 简单demo import pymysql connect pymysql.connect(host"localhost",port3306,user"root",password"root",database&quo…...
在Spring Boot中如何使用Freemaker模板引擎
在 Spring Boot 中使用 FreeMarker 模板引擎可以帮助你创建动态的 Web 页面。以下是详细的步骤和示例代码,介绍如何在 Spring Boot 项目里集成和使用 FreeMarker。 1. 添加依赖 如果你使用的是 Maven 项目,需要在 pom.xml 文件中添加 FreeMarker 相关依赖。Spring Boot 提供…...
数据驱动未来!天合光能与永洪科技携手开启数字化新篇章
在信息化时代的今天,企业间的竞争早就超越了传统产品与服务的范畴,新的核心竞争力即——数据处理能力和信息技术的应用。作为数据技术领域的领军者,永洪科技凭借其深厚的技术积累和丰富的行业经验,成功助力天合光能实现数字化升级…...
JavaScript数据结构-模拟链表
在JavaScript中没有链表这种数据结构,但是我们可以用对象(Object)模拟链表,下面让我们先了解链表是什么。 链表(Linked List)是一种基础的数据结构,由一系列节点(Node)组成,每一个节…...
使用 Apache Jena 构建 RDF 数据处理与查询服务
一、引言 随着语义网和知识图谱技术的不断发展,RDF(Resource Description Framework)作为一种用于描述资源的框架,被广泛应用于知识表示和数据集成。Apache Jena 是一个功能强大的 Java 框架,用于处理 RDF 数据和 SPA…...
tableau之网络图和弧线图
一、网络图 概念 网络图(Network Graph),也称为网络可视化,是数据可视化的一种形式,用于显示实体(节点)之间的关系(边)。这种图表通过节点和边的结构揭示数据中的复杂关…...
el-date-picker 组件限制禁止选择当前时间之前的时间
页面代码 <el-date-pickerv-model"xxx.startTime"type"datetime"placeholder"请选择开始时间"value-format"YYYY-MM-DD HH:mm:ss"clearable:disabledDate"disabledDateFn":disabled-hours"disabledHours":dis…...
Linux网络数据包接收:原理、流程与优化策略
在当今数字化时代,网络已成为计算机系统不可或缺的部分。无论是日常的网页浏览、文件传输,还是大规模数据中心的高效通信,网络数据包的收发都在其中扮演着重要角色。对于 Linux 系统而言,深入理解网络数据包的接收过程,…...
django model.object.filter 不等于多个值
关于Django中QuerySet.filter()的使用问题。首先,我会分别针对“不等于多个值”的代码开发问题和可能遇到的报错问题给出解答。 代码开发问题:QuerySet.filter()不等于多个值 在Django中,如果你想在查询中排除多个值,可以使用__i…...
sklearn中的决策树-分类树:实例-分类树在合成数据集上的表现
分类树实例:分类树在合成数据集上的表现 代码分解 在不同结构的据集上测试一下决策树的效果(二分型,月亮形,环形) 导入 import numpy as np from matplotlib import pyplot as plt from matplotlib.colors import Li…...
