Kotlin笔记(二):标准函数,静态方法,延迟初始化,密封类

1. 标准函数

 Kotlin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数. 前面用到的let函数就算一个标准函数,它的主要作用就是配合?.操作符来进行辅助判空处理.

1.1 with函数

 with函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。示例代码如下:

val result = with(obj) {// 这里是obj的上下文"value" // with函数的返回值
}

 with函数可以在连续调用同一个对象的多个方法时让代码变得更加精简,下面我们来看一个具体的例子:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val builder = StringBuilder()
builder.append("Start eating fruits.\n")
for (fruit in list) {builder.append(fruit).append("\n")
}
builder.append("Ate all fruits.")
val result = builder.toString()
println(result)

 我们连续调用了很多次builder对象的方法。其实这个时候就可以考虑使用with函数来让代码变得更加精简,如下所示:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = with(StringBuilder()) {append("Start eating fruits.\n")for (fruit in list) {append(fruit).append("\n")}append("Ate all fruits.")toString()
}
println(result)

 首先我们给with函数的第一个参数传入了一个StringBuilder对象,那么接下来整个Lambda表达式的上下文就会是这个StringBuilder对象。于是我们在Lambda表达式中就不用再像刚才那样调用
builder.append()和builder.toString()方法了,而是可以直接调用append()和toString()方法。

1.2 run函数

 run函数的用法和使用场景其实和with函数是非常类似的,只是稍微做了一些语法改动而已。首先run函数通常不会直接调用,而是要在某个对象的基础上调用;其次run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其他方面和with函数是一样的,包括也会使用Lambda表达式中的最后一行代码作为返回值返回。示例代码如下:

val result = obj.run {// 这里是obj的上下文"value" // run函数的返回值
}

1.3 apply函数

 apply函数和run函数也是极其类似的,都要在某个对象上调用,并且只接收一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。示例代码如下:

val result = obj.apply {// 这里是obj的上下文
}
// result == obj

 那么现在我们再使用apply函数来修改一下吃水果的这段代码,如下所示:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().apply {append("Start eating fruits.\n")for (fruit in list) {append(fruit).append("\n")}append("Ate all fruits.")
}
println(result.toString())

 注意这里的代码变化,由于apply函数无法指定返回值,只能返回调用对象本身,因此这里的
result实际上是一个StringBuilder对象,所以我们在最后打印的时候还要再调用它的toString()方法才行


2. 静态方法

 和绝大多数主流编程语言不同的是,Kotlin却极度弱化了静态方法这个概念.因为Kotlin提供了比静态方法更好用的语法特性,那就是单例类.
 像工具类这种功能,在Kotlin中就非常推荐使用单例类的方式来实现,比如上述的Util工具
类,如果使用Kotlin来实现的话就可以这样写:

object Util {fun doAction() {println("do action")}
}

 虽然这里的doAction()方法并不是静态方法,但是我们仍然可以使用Util.doAction()的
方式来调用,这就是单例类所带来的便利性。
 不过,使用单例类的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,而如
果我们只是希望让类中的某一个方法变成静态方法的调用方式该怎么办呢?这个时候就可以使用companion object了,示例如下:

class Util {fun doAction1() {println("do action1")}companion object {fun doAction2() {println("do action2")}}
}

 这里首先我们将Util从单例类改成了一个普通类,然后在类中直接定义了一个doAction1()
方法,又在companion object中定义了一个doAction2()方法。现在这两个方法就有了本质的区别,因为doAction1()方法是一定要先创建Util类的实例才能调用的,而doAction2()方法可以直接使用Util.doAction2()的方式调用。
 不过,doAction2()方法其实也并不是静态方法,companion object这个关键字实际上会在Util类的内部创建一个伴生类,而doAction2()方法就是定义在这个伴生类里面的实例方法。只是Kotlin会保证Util类始终只会存在一个伴生类对象,因此调用Util.doAction2()方法实际上就是调用了Util类中伴生对象的doAction2()方法。
 由此可以看出,Kotlin确实没有直接定义静态方法的关键字,但是提供了一些语法特性来支持类
似于静态方法调用的写法,这些语法特性基本可以满足我们平时的开发需求了。
 如果你确确实实需要定义真正的静态方法, Kotlin仍然提供了两种实现方式:注解和顶层
方法

2.1 注解

 先来看注解,前面使用的单例类和companion object都只是在语法的形式上模仿了静态方法
的调用方式,实际上它们都不是真正的静态方法。因此如果你在Java代码中以静态方法的形式
去调用的话,你会发现这些方法并不存在。而如果我们给单例类或companion object中的方
法加上@JvmStatic注解,那么Kotlin编译器就会将这些方法编译成真正的静态方法,如下所
示:

class Util {fun doAction1() {println("do action1")}companion object {@JvmStaticfun doAction2() {println("do action2")}}
}

 注意,@JvmStatic注解只能加在单例类或companion object中的方法上,如果你尝试加在
一个普通方法上,会直接提示语法错误。
 由于doAction2()方法已经成为了真正的静态方法,那么现在不管是在Kotlin中还是在Java
中,都可以使用Util.doAction2()的写法来调用了。

2.2 顶层方法

 顶层方法指的是那些没有定义在任何类中的方法,比如我们编写的main()方法。Kotlin编译器会将所有的顶层方法全部编译成静态方法,因此只要你定义了一个顶层方法,那么它就一定是静态方法。
 想要定义一个顶层方法,首先需要创建一个Kotlin文件。对着任意包名右击 → New → Kotlin
File/Class,在弹出的对话框中输入文件名Helper即可。注意创建类型要选择File.
 现在我们在这个文件中定义的任何方法都会是顶层方法,比如这里我就定义一个doSomething()方法吧,如下所示:

fun doSomething() {println("do something")
}

 在Kotlin代码中调用的话,那就很简单了,所有的顶层方法都可以在任何位置被直接调用,不用管包名路径,也不用创建实例,直接键入doSomething()即可.
 如果是在Java代码中调用,你会发现是找不到doSomething()这个方法的,因为Java中没有顶层方法这个概念,所有的方法必须定义在类中。那么这个doSomething()方法被藏在了哪里呢?我们刚才创建的Kotlin文件名叫作Helper.kt,于是Kotlin编译器会自动创建一个叫作HelperKt的Java类,doSomething()方法就是以静态方法的形式定义在HelperKt类里面的,因此在Java中使用HelperKt.doSomething()的写法来调用就可以了.

3. 延迟初始化

3.1 lateinit关键字

 Kotlin语言的许多特性,包括变量不可变,变量不可为空,等等。这些特性都是为了尽可能地保证程序安全而设计的,但是有些时候这些特性也会在编码时给我们带来不少的麻烦。

class MainActivity : AppCompatActivity(), View.OnClickListener {private var adapter: MsgAdapter? = nulloverride 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: MsgAdapteroverride fun onCreate(savedInstanceState: Bundle?) {...adapter = MsgAdapter(msgList)...}override fun onClick(v: View?) {...adapter.notifyItemInserted(msgList.size - 1)...}
}

 当然,使用lateinit关键字也不是没有任何风险,如果我们在adapter变量还没有初始化的情况下就直接使用它,那么程序就一定会崩溃,并且抛出一个UninitializedPropertyAccessException异常.

3.2 ::object.isInitialized 方法

 另外,我们还可以通过代码来判断一个全局变量是否已经完成了初始化,这样在某些时候能够
有效地避免重复对某一个变量进行初始化操作,示例代码如下:

class MainActivity : AppCompatActivity(), View.OnClickListener {private lateinit var adapter: MsgAdapteroverride fun onCreate(savedInstanceState: Bundle?) {...if (!::adapter.isInitialized) {adapter = MsgAdapter(msgList)}...}
}

 具体语法就是这样,::adapter.isInitialized可用于判断adapter变量是否已经初始
化。虽然语法看上去有点奇怪,但这是固定的写法。然后我们再对结果进行取反,如果还没有
初始化,那么就立即对adapter变量进行初始化,否则什么都不用做。


4. 密封类

4.1 密封类的使用场景

 新建一个Kotlin文件,文件名就叫Result.kt好了,然后在这个文件中编写如下代码:

interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result

 接下来再定义一个getResultMsg()方法,用于获取最终执行结果的信息,代码如下所示:

fun getResultMsg(result: Result) = when (result) {is Success -> result.msgis Failure -> result.error.messageelse -> throw IllegalArgumentException()
}

 比较让人讨厌的是,接下来我们不得不再编写一个else条件,否则Kotlin编译器会认为这里缺少条件分支,代码将无法编译通过。但实际上Result的执行结果只可能是Success或者Failure,这个else条件是永远走不到的,所以我们在这里直接抛出了一个异常,只是为了满足Kotlin编译器的语法检查而已。
 另外,编写else条件还有一个潜在的风险。如果我们现在新增了一个Unknown类并实现Result接口,用于表示未知的执行结果,但是忘记在getResultMsg()方法中添加相应的条件分支,编译器在这种情况下是不会提醒我们的,而是会在运行的时候进入else条件里面,从而抛出异常并导致程序崩溃.
 不过好消息是,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.msgis Failure -> "Error is ${result.error.message}"
}

 这是因为当在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类所对应的条件全部处理。这样就可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的情况。而如果我们现在新增一个Unknown类,并也让它继承自Result,此时getResultMsg()方法就一定会报错,必须增加一个Unknown的条件分支才能让代码编译通过。
 这就是密封类主要的作用和使用方法了。另外再多说一句,密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的

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

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

相关文章

Empowering Low-Light Image Enhancer through Customized Learnable Priors 论文阅读笔记

中科大、西安交大、南开大学发表在ICCV2023的论文,作者里有李重仪老师和中科大的Jie Huang(ECCV2022的FEC CVPR2022的ENC和CVPR2023的ERL的一作)喔,看来可能是和Jie Huang同一个课题组的,而且同样代码是开源的&#xf…

快速排序、归并排序、基数排序

快速排序 算法思想 图 1-1 即确定一个基准值(一般为数组中间位置的元素,或者自定义),让待排序数组中所有比基准值小的元素放到基准值左边的位置,所有比基准值大的元素放到基准值右边的位置,这样一趟排序下…

iOS——Manager封装网络请求

在之前的项目里,我们都是把网络请求写在viewController的viewDidLoad,而实际中使用的时候并不能这么简单,对于不同的需要,我们需要有不同的网络请求。所以我们可以用单例模式创建一个全局的Manager类,用实例Manager来执…

设计模式~备忘录模式(memento)-22

目录  (1)优点: (2)缺点: (3)使用场景: (4)注意事项: (5)应用实例: 代码 备忘录模式(memento) 备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对…

给课题组师弟师妹的开荒手册(终篇)

0 写在前面 终于,在结束收尾工作后敲下了开荒手册的终篇,自己三年研究生生活过的离理想中的完美还差很多,不过胜在完整,哈哈,小满胜万全嘛。希望以自己不太完美的经历为例,抛我的砖,引师弟师妹…

解决Dev C++编译或运行报错 Source file not compiled

最近在研究青少年编程,用到DevC,写了个程序点击编译并运行后,我得到了一个错误消息:Source file not compiled。网上查了一下:原因是bloodshed Dev C与Windows10或者11不兼容所以才会报:Source file not co…

模块电源(四):可调DC-DC

一、DC-DC典型应用 以DC-DC转换器SCT2432数据手册为例,典型应用电路如下图所示: 其中,输出电压为: , DC-DC转换器中, 反馈电压是指反馈回路中的信号电压,用于控制输出电压与设定电压之间的误差&…

使用 AWS DataSync 进行跨区域 AWS EFS 数据传输

如何跨区域EFS到EFS数据传输 部署 DataSync 代理 在可以访问源 EFS 和目标 EFS 的源区域中部署代理。转至AWS 代理 AMI 列表并按 AWS 区域选择您的 AMI。对于 us-west-1,单击 us-west-1 前面的启动实例。 启动实例 2. 选择您的实例类型。AWS 建议使用以下实例类型之…

EVT/DVT/PVT/MP是指在制造行业一个产品

EVT/DVT/PVT/MP是指在制造行业一个产品研发导入从试产到量产的不同阶段: EVT:Engineering Verification Test工程验证测试阶段 DVT:Design Verification Test设计验证测试 PVT:Production Verification Test 小批量生产验证测试…

for循环中循环一次提交一次 insert update 关闭事务 spring springboot mybatis

省流: 在方法上直接加如下注解: Transactional(propagation Propagation.NOT_SUPPORTED) public void t1(){//业务代码 } 正文: 在测试的时候,有时候会希望在for循环中,代码循环一次就提交一次事务。 方法一&#…

【计算机毕设选题推荐】网络在线考试系统SpringBoot+SSM+Vue

前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 项目名 网络在线考试系统 技术栈 SpringBootSSMVueMySQLMaven 文章目录 一、网络在线考试系统-环境…

从永远到永远-吉他和弦替代原理

吉他和弦替代原理 0.背景1.和弦分类1.主和弦2.属和弦3.属七和弦4.下属和弦5.副属和弦6.离调和弦 2.功能组化分依据1.划分依据及分组2.Ⅵm、Ⅲm级 3.替代1.传统的和弦链接与替代2.离调和弦替代3.属和弦替代1.降五级替代2.减七和弦替代3.重属和弦 999 参考资料 0.背景 1.和弦分类…

哈佛教授因果推断力作:《Causal Inference: What If 》pdf下载

因果推断是一项复杂的科学任务,它依赖于多个来源的三角互证和各种方法论方法的应用,是用于解释分析的强大建模工具,同时也是机器学习领域的热门研究方向之一。 今天我要给大家推荐的这本书,正是因果推断领域必读的入门秘籍&#…

windows环境下搭建redis5.x集群

下载windows版本redis5.x redis.windows.conf内容修改如下: # 端口 (注意:改为每个文件夹对应的端口,分别为6379、6380、6381、6382、6383、6384) port 6379 # 允许创建集群 appendonly yes cluster-enabled…

工控网络协议模糊测试:用peach对modbus协议进行模糊测试

0x00 背景 本人第一次在FB发帖,进入工控安全行业时间不算很长,可能对模糊测试见解出现偏差,请见谅。 在接触工控安全这一段时间内,对于挖掘工控设备的漏洞,必须对工控各种协议有一定的了解,然后对工控协议…

ros学习笔记(1)Mac本地安装虚拟机,安装Ros2环境

Ros与Linux的关系 Ros环境基于Linux系统内核 我们平时用的是Linux发行版,centos,ubuntu等等,机器人就用了ubunut 有时候我们经常会听到ubunue的版本,众多版本中,有一些是长期维护版TLS,有一些是短期维护…

面试算法26:重排链表

问题 给定一个链表,链表中节点的顺序是L0→L1→L2→…→Ln-1→Ln,请问如何重排链表使节点的顺序变成L0→Ln→L1→Ln-1→L2→Ln-2→…? 分析 首先把链表分成前后两半。在示例链表中,前半段链表包含1、2、3这3个节点&#xff0c…

分布式系统部署Redis

文章目录 一、单点问题二、主从模式概念配置主从结构查看主从节点断开从属关系拓扑结构主从复制原理replication复制offset偏移量 全量复制和部分复制全量复制部分复制 实时复制redis主节点无法重启 三、主从哨兵模式哨兵概念监控程序人工恢复自动恢复为什么是哨兵集合使用dock…

Stm32_标准库_15_串口蓝牙模块_手机与蓝牙模块通信_BUG修复

代码&#xff1a; #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "Time.h" #include "Function.h" #include <stdio.h> #include <…

数字孪生技术在智慧城市应用的推进建议

&#xff08;一&#xff09;坚持需求牵引&#xff0c;强场景重实效 必须始终坚持以人为本、场景导向、需求牵引&#xff0c;站在供给侧结构性改革的角度&#xff0c;突出以用促建&#xff0c;强调建用并重&#xff0c;真正发挥数字孪生城市应用建设的实效。从构建数字孪生创新…