Kotlin基础知识_06-变量延迟初始化_密封类
Kotlin基础知识_06-变量延迟初始化&密封类
1. 使用 lateinit 关键字对变量进行延迟初始化
之前有介绍过, kotlin 不能很好的判断全局变量是否为null
的情形,比如下面的code:
private var name: String? = null
fun updateName(newName: String) {
name = newName
}
fun main() {
updateName("ZhangSan")
println("The name length: ${name.length}")
}
即便我们确认会在打印 name
的长度前调用 updateName("ZhangSan")
方法给全局变量 name
赋值,但是编译器还是会报错:
它希望我们能够使用判空操作 ?.
来获取长度,即:
println("The name length: ${name?.length}")
这样其实很繁琐,因为有些时候,有些变量在定义的时候就是不想给它初始化,初始化会放到某个阶段去做,比如在Android Activity#onCreate()
方法执行findViewById()
操作,按照Kotlin的语法,这个时候要么给 findViewById()
的控件声明为null, 要么就是给它指定一个默认值,声明为null太麻烦,而且后面使用的时候还是需要用到 ?.
操作符去判断;而给控件给定一个默认值更不合理,因为界面都还没有onCreate
哪来的初始值。
kotlin 允许给变量添加一个 lateinit
关键字来做延迟初始化:
ex:
private lateinit var name: String
fun updateName(newName: String) {
name = newName
}
fun main() {
updateName("ZhangSan")
println("The name length: ${name.length}")
}
运行:
The name length: 8
可以看到,这样就不需要在使用 name.length
的时候再使用判空操作符了。
不过要注意,既然已告诉编译器这个变量会延迟初始化,那就一定要做初始化,比如把 updateName("ZhangSan")
这行代码去掉,那代码还是会崩溃。
private lateinit var name: String
fun updateName(newName: String) {
name = newName
}
fun main() {
// updateName("ZhangSan")
println("The name length: ${name.length}")
}
运行,编译器直接报错:
当然,也可以通过代码来判断一个全局变量是否已经完成了初始化,这样在某些时候能够有效地避免重复对某一个变量进行初始化操作:
ex:
private lateinit var name: String
fun updateName(newName: String) {
name = newName
}
fun main() {
// updateName("ZhangSan")
if (!::name.isInitialized) {
println("Tips: name has not init")
name = "ZhangSan"
} else {
println("name has init")
}
println("The name length: ${name.length}")
}
运行:
Tips: name has not init
The name length: 8
具体语法就是这样,::name.isInitialized
, 我们对结果取反,判断该变量是否已进行过初始化。
2. 密封类 sealed class
有时候会有这样一个场景:经常需要对输入的类型做判断,然后做具体处理,
比如我的视频播放器支持多种编解码器(FFmpegCodec, MediaCodec, GStreamerCodec):
interface Codec {}
class FFmpegCodec implements Codec {}
class MediaCodec implements Codec {}
class GStreamerCodec implements Codec {}
当用户播放视频时,他可能会选择切换其中一种编解码器来播放视频,这个时候就可能会走到类似于下面这样的代码块里面:
/**
* 根据输入的解码器来解码视频
*
* @param codec 输入的编码器类型
* @return 解码的结果, 0代表成功,其它值代表失败
*/
public static int decodeVideo(Codec codec) {
if (codec instanceof FFmpegCodec) {
System.out.println("Codec is FFmpegCodec");
return 0;
} else if (codec instanceof MediaCodec) {
System.out.println("Codec is MediaCodec");
return 0;
} else if (codec instanceof GStreamerCodec) {
System.out.println("Codec is GStreamerCodec");
return 0;
} else {
System.out.println("Unsupport codec, decode fail");
return -1;
}
}
我们会对已支持的编码器做判断,然后进行解码,不支持的编码器就直接返回 -1
;
这本来没有问题,只是这个else
语句可能永远都走不进去,因为界面上可能只有三个选项供用户选择:
- FFmpegCodec
- MediaCodec
- GStreamerCodec
所以无论如何都不可能选到 else
这种情况,写else
只是因为 if
语句块的要求,必须要有else
这种代码块,否则少了else
代码块的返回值,编译器报错。
这样看,这个else
代码块其实是多余的。
另外,这样写还会有一个隐含的问题,比如我现在新支持了一种编码器:HardwareCodec
:
interface Codec {}
class FFmpegCodec implements Codec {}
class MediaCodec implements Codec {}
class GStreamerCodec implements Codec {}
class HardwareCodec implements Codec {}
刚好代码里做编码器类型判断的地方有很多处,其中有一处并没有判断到 HardwareCodec
这种case, 这样就可能导致代码有异常情况发生,本来预期要对 HardwareCodec
这种类型做处理,结果稀里糊涂走到之前定义的else
代码块里,然后程序就报错了。
kotlin 提供了一个名为密封类 sealed class 的语法结构来优化此问题。
新建一个名为 Codec.kt
的kotlin file, 在其中添加如下code:
sealed class Codec
class FFmpegCodec : Codec()
class MediaCodec : Codec()
class GStreamerCodec : Codec()
然后在另一个kotlin file 中编写测试code:
/**
* 根据输入的解码器来解码视频
*
* @param codec 输入的编码器类型
* @return 解码的结果, 0代表成功,其它值代表失败
*/
fun decodeVideo(codec: Codec): Int {
when (codec) {
is FFmpegCodec -> {
println("Codec is FFmpegCodec")
return 0
}
is MediaCodec -> {
println("Codec is MediaCodec")
return 0
}
is GStreamerCodec -> {
println("Codec is GStreamerCodec")
return 0
}
}
}
有没有发现,不用写 else
语句块了,因为我们将 Codec
声明为密封类后,只让 FFmpegCodec
, MediaCodec
,GStreamerCodec
继承了它,等于告诉了kotlin编译器,我这个密封类,只会有以上三个子类,不会再有其它类型了,所以自然也不着写 else
语句块了。
另外,如果你新加了一个类型 HardwareCodec
:
sealed class Codec
class FFmpegCodec : Codec()
class MediaCodec : Codec()
class GStreamerCodec : Codec()
class HardwareCodec: Codec()
如果在代码里有某个判断Codec
类型的地方,你忘记添加 HardwareCodec
类型了,编译器就直接报错了:
提示你when表达式还有条件未做判断,必须添加 HardwareCodec
条件判断或 else
条件判断才可以,这样你就不用担心代码里有某个地方你忘记添加了。
这里我们添加 HardwareCodec
条件判断,编译器就可以正常通过编译了。
/**
* 根据输入的解码器来解码视频
*
* @param codec 输入的编码器类型
* @return 解码的结果, 0代表成功,其它值代表失败
*/
fun decodeVideo(codec: Codec): Int {
when (codec) {
is FFmpegCodec -> {
println("Codec is FFmpegCodec")
return 0
}
is MediaCodec -> {
println("Codec is MediaCodec")
return 0
}
is GStreamerCodec -> {
println("Codec is GStreamerCodec")
return 0
}
is HardwareCodec -> {
println("Codec is HardwareCodec")
return 0
}
}
}
密封类一般用来表示受限的类继承结构,当一个值为有限的几种类型, 而不能有任何其他类型时用密封类就会很有用。
这里需要注意,其实FFmpegCodec
, MediaCodec
,GStreamerCodec
, 这三个类你不写在同一个文件中也是可以的,早期的kotlin 版本是不支持的,目前我测试的kotlin 版本(1.9.0)是支持把密封类的子类写到其它文件当中去的,事实上将三个类的代码写到同一个文件中也显得比较臃肿。
<完>