Kotlin(十三) 延迟初始化和密封类

对变量延迟初始化

延迟初始化使用的是**lateinit**关键字,它可以告诉Kotlin编译器,我会在晚些时候对这个变量进行初始化,这样就不用在一开始的时候将它赋值为null了。示例如下:

Kotlin 复制代码
class MainActivity : AppCompatActivity() {

    private lateinit var student: Student


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        student = Student("01", 1)
        student.doHomework()
    }


}

当我们使用**lateinit**关键字对Student对象进行延迟初始化时,我们就不需要像之前章节提到过的一样,再对student进行判空处理,直接可以调用doHomework方法。

当然,当我们使用**lateinit**关键字也不是没有任何风险的,如果我们在student变量还没初始化时调用,那么程序还是会崩溃的,并且会抛出UninitializedPropertyAccessException异常。

所以,当你对一个全局变量使用了**lateinit**关键字时,请一定要确保它在被任何地方调用之前已经完成了初始化工作,否则Kotlin将无法保证程序的安全性。

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

Kotlin 复制代码
class MainActivity : AppCompatActivity() {

    private lateinit var student: Student


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (!::student.isInitialized){
            student = Student("01", 1)
        }
        student.doHomework()
    }


}

具体语法就是这样,

复制代码
::student.isInitialized

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

使用密封类优化代码

首先来了解一下密封类具体的作用,这里我们来看一个简单的例子。新建一个Kotlin文件,文件名就叫Result.kt好了,然后在这个文件中编写如下代码:

Kotlin 复制代码
interface Result
class Success(val msg: String) : Result
class Failure(val error: String) : Result

这里定义了一个Result接口,用于表示某个操作的执行结果,接口中不用编写任何内容。然后定义了两个类去实现Result接口:一个Success类用于表示成功时的结果,一个Failure类用于表示失败时的结果,这样就把准备工作做好了。

然后定义一个getMsg()方法,代码如下所示:

Kotlin 复制代码
fun getMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error
    else -> throw Exception()
}

getMsg()方法中接收一个Result参数。我们通过when语句来判断:如果Result属于Success,那么就返回成功的消息;如果Result属于Failure,那么就返回错误信息。到目前为止,代码都是没有问题的,但比较让人讨厌的是,接下来我们不得不再编写一个else条件,否则Kotlin编译器会认为这里缺少条件分支,代码将无法编译通过。但实际上Result的执行结果只可能是Success或者Failure,这个else条件是永远走不到的,所以我们在这里直接抛出了一个异常,只是为了满足Kotlin编译器的语法检查而已。

另外,编写else条件还有一个潜在的风险。如果我们现在新增了一个Unknown类并实现Result接口,用于表示未知的执行结果,但是忘记在getMsg()方法中添加相应的条件分支,编译器在这种情况下是不会提醒我们的,而是会在运行的时候进入else条件里面,从而抛出异常并导致程序崩溃。

当然,这种为了满足编译器的要求而编写无用条件分支的情况不仅在Kotlin当中存在,在Java或者是其他编程语言当中也普遍存在。

不过好消息是,Kotlin的密封类可以很好地解决这个问题,下面我们就来学习一下。

密封类的关键字是sealed class,它的用法同样非常简单,我们可以轻松地将Result接口改造成密封类的写法:

Kotlin 复制代码
sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: String) : Result()

fun getMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error
}

可以看到,代码并没有什么太大的变化,只是将interface关键字改成了**sealed class**。另外,由于密封类是一个可继承的类,因此在继承它的时候需要在后面加上一对括号。

那么改成密封类之后有什么作用呢?你会发现现在getMsg()方法中的else条件已经不再需要了.

并且如果我们现在新增一个Unknown类,并也让它继承自Result,此时getMsg()方法就一定会报错,必须增加一个Unknown的条件分支才能让代码编译通过。

这就是密封类主要的作用和使用方法了。另外再多说一句,密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的。

相关推荐
Scott9999HH21 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社1 天前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海1 天前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖1 天前
JDK 26 新特性详解
java·开发语言
马优晨1 天前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区1 天前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大1 天前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai1 天前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜1 天前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa1 天前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio