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 适配器的写法更加规范也更加推荐。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24205.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简单介绍 SSL 证书类型: DV、OV、EV 的区别

SSL证书类型DV、OV、EV 区别&#xff1a; DV(域名验证型)SSL证书 OV(组织验证型)SSL证书 EV(扩展验证型)SSL证书

深度解析SmartGBD助力Android音视频数据接入GB28181平台

在当今数字化时代&#xff0c;视频监控与音视频通信技术在各行各业的应用愈发广泛。GB28181协议作为中国国家标准&#xff0c;为视频监控设备的互联互通提供了规范&#xff0c;但在实际应用中&#xff0c;许多Android终端设备并不具备国标音视频能力&#xff0c;这限制了其在相…

1分钟用DeepSeek编写一个PDF转Word软件

一、引言 如今&#xff0c;在线工具的普及让PDF转Word成为了一个常见需求&#xff0c;常见的pdf转word工具有收费的wps&#xff0c;免费的有pdfgear&#xff0c;见下文&#xff1a; PDFgear:一款免费的PDF编辑、格式转化软件-CSDN博客 还有网上在线的免费pdf转word工具smallp…

PyCharm Professional 2025 安装配置全流程指南(Windows平台)

一、软件定位与核心功能 PyCharm 2025 是 JetBrains 推出的智能 Python IDE&#xff0c;新增深度学习框架自动补全、实时性能热力图等功能1。相较于社区版&#xff0c;专业版支持&#xff1a; Web开发&#xff08;Django/Flask&#xff09;数据库工具&#xff08;PostgreSQL/…

DeepSeek回答:AI时代Go语言学习路线

最近有小伙伴经常会问&#xff1a;**该如何学习入门Go语言&#xff1f;怎样提升Go语言Coding水平&#xff1f;**这篇文章我们就使用DeepSeek来梳理下Go语言在AI时代的学习路线。 向DeepSeek提问的问题原文&#xff1a; 你现在是一名资深的Go语言工程师&#xff0c;精通Go语言并…

OpenGL ES -> GLSurfaceView绘制点、线、三角形、正方形、圆(顶点法绘制)

XML文件 <?xml version"1.0" encoding"utf-8"?> <com.example.myapplication.MyGLSurfaceViewxmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"…

嵌入式项目:STM32刷卡指纹智能门禁系统

本文详细介绍基于STM32的刷卡指纹智能门禁系统。 获取资料/指导答疑/技术交流/选题/帮助&#xff0c;请点链接&#xff1a; https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt 1 系统功能 1.1 功能概述 本系统由STM32硬件端&#xff08;下位机&#xff09;…

Dubbo RPC 原理

一、Dubbo 简介 Apache Dubbo 是一款高性能、轻量级的开源 RPC 框架&#xff0c;支持服务治理、协议扩展、负载均衡、容错机制等核心功能&#xff0c;广泛应用于微服务架构。其核心目标是解决分布式服务之间的高效通信与服务治理问题。 二、Dubbo 架构设计 1. 核心组件 Prov…

RBAC授权

4 RBAC授权 4.1 什么是RBAC 在Kubernetes中&#xff0c;所有资源对象都是通过API进行操作&#xff0c;他们保存在etcd里。而对etcd的操作我们需要通过访问kube-apiserver来实现&#xff0c;上面的Service Account其实就是APIServer的认证过程&#xff0c;而授权的机制是通过RBA…

C/C++ | 每日一练 (4)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 C/C | 每日一练 (4)题目参考答案基础容器序列容器std:…

HarmonyOS 5.0应用开发——鸿蒙接入高德地图实现POI搜索

【高心星出品】 文章目录 鸿蒙接入高德地图实现POI搜索运行结果&#xff1a;准备地图编写ArkUI布局来加载HTML地图 鸿蒙接入高德地图实现POI搜索 在当今数字化时代&#xff0c;地图应用已成为移动设备中不可或缺的一部分。随着鸿蒙系统的日益普及&#xff0c;如何在鸿蒙应用中…

Linux系统:服务器常见服务默认IP端口合集

服务器的默认IP端口取决于所使用的协议和服务类型。以下是一些常见服务和协议的默认端口&#xff1a; 服务端口实例&#xff1a; HTTP服务 默认端口&#xff1a;80 说明&#xff1a;用于普通的HTTP网页访问。例如&#xff0c;访问 http://example.com 时&#xff0c;默认使用8…

一周学会Flask3 Python Web开发-flask3上下文全局变量session,g和current_app

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili flask3提供了session,g和current_app上下文全局变量来方便我们操作访问数据。 以下是一个表格&#xff0c;用于比较Flask中的…

学习路程四 向量数据库Milvus安装与连接

前序 在之前&#xff0c;已经简单完成了文档的加载&#xff0c;分割&#xff0c;向量化这些步骤&#xff0c;最后得到了结果。但是这些数据都是一次性的。假设一个律师所&#xff0c;有几千上万份卷宗&#xff0c;不可能每次使用都重新向量化数据吧。 所以我们需要有一个地方存…

Docker 搭建 Nginx 服务器

系列文章目录 Docker 搭建 Nginx 服务器 系列文章目录前言一、准备工作二、设置 Nginx 容器的目录结构三、启动一个临时的 Nginx 容器来复制配置文件四、复制 Nginx 配置文件到本地目录五、删除临时 Nginx 容器六、创建并运行 Nginx 容器&#xff0c;挂载本地目录七、修改 ngin…

centos9安装k8s集群

以下是基于CentOS Stream 9的Kubernetes 1.28.2完整安装流程&#xff08;containerd版&#xff09;&#xff1a; 一、系统初始化&#xff08;所有节点执行&#xff09; # 关闭防火墙 systemctl disable --now firewalld# 关闭SELinux sed -i "s/SELINUXenforcing/SELINU…

WebSocket connection failed 解决

WebSocket connection failed 解决 前言 这里如果是新手小白不知道 WebSocket 是什么的&#xff1f; 怎么使用的&#xff1f;或者想深入了解的 那可以 点击这里 几分钟带你快速了解并使用&#xff0c;已经一些进阶讲解&#xff1b; WebSocket&#xff0c;多应用于需要双向数据…

基于大数据爬虫数据挖掘技术+Python的线上招聘信息分析统计与可视化平台(源码+论文+PPT+部署文档教程等)

博主介绍&#xff1a;CSDN毕设辅导第一人、全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringB…

java给钉钉邮箱发送邮件

1.开通POP和IMAP 2.引入pom <dependency><groupId>javax.mail</groupId><artifactId>mail</artifactId><version>1.4.7</version> </dependency>3.逻辑 String host "smtp.qiye.aliyun.com"; String port "…

五、AIGC大模型_04LLaMA-Factory基础知识与SFT实战

1、LLaMA-Factory 基本介绍 1.1 定义 LLaMA-Factory 是一个开源的大型语言模型&#xff08;LLM&#xff09;微调框架&#xff0c;旨在帮助开发者和研究人员轻松地对预训练语言模型进行定制化训练和优化 1.2 功能特点 支持多种预训练模型 LLaMA Factory 支持超过 100 种主流的…