Kotlin延迟初始化

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Kotlin延迟初始化

m0_63970488   2022-09-28 我要评论

一、lateinit延迟初始化关键字

Kotlin中很多语法特性,如变量不可变,变量不可为空,等等 这些特性都是为了尽可能地保证程序安全而设计的,比如你的类中存在很多全局变量实例,为了保证它们的能够满足Kotlin的空指针检查语句标准,你不得不做非空判断保护,即使你非常确定它们不会为空。

下面距离看一下 :

class MainActivity : AppCompatActivity() {
    private var s: String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        s = "test"
        Log.d("TAG", "onCreate: ${s!!.length}")
    }
}

我们将s 设置为了全局变量 , 但是它的赋值工作在onCreate()方法进行的,因此不得不将s赋值为null。

虽然你确定在打印前已经将s赋值成功,但是打印s的长度仍然要进行判空处理才行,否则编译不通过。

当你的代码中越来越多的全局变量实例时,这个问题就会变得越来越明显,到时候可能必须写大量额外判空处理的代码,却只是为了满足Kotlin的编译要求。

幸运的是,这个问题有解决办法的且非常之简单,就是对全局变量进行延迟初始化。

初始化使用的关键字 lateinit ,它可以高速编译器,我会在晚些时候对这个变量进行初始化,这样就不用一开始就赋值为null了。

class MainActivity : AppCompatActivity() {
    private lateinit var s: String
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        s = "test"
        Log.d("TAG", "onCreate: ${s.length}")
    }
}

可以看到,加上了lateinit关键字 ,这样一开始就不用赋值为null了,打印长度的时候也不用进行判空处理了,当然使用lateinit关键字 也不是没有风险,如果没有进行赋值,那么程序一定会崩溃,并抛出异常。

另外,我们可以通过代码来判断全局变量是否已完成了初始化工作,这样某些时候可以有效避免重复对某一个变量进行初始化操作。

class MainActivity : AppCompatActivity() {
    private lateinit var s: String
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (!::s.isInitialized) {
            s = "test"
        }
        Log.d("TAG", "onCreate: ${s.length}")
    }
}

二、使用密封类优化代码

新建一个Kotlin文件,代码如下 :

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编译不通过,其实代码永远也走不到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()
fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error.message
}

代码没什么变化,只是将interface改成了 sealed class。密封类是一个可继承的类,因此在继承它的时候还需要加上一对括号。

密封类的优点是,可以再getResultMsg() 方法中取消else条件语句。

为什么取消掉else条件语句还能编译通过呢,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求将每一个子类所对应的条件全部处理。这样就可以保证,即使没有else条件,也不可能出现漏写的情况,如果现在新增一个Unknown类,并也让它继承自Result,此时getResultMsg()方法就一定会报错,必须添加Unknown语句条件才能编译通过。

注意:密封类及其所有子类只能定义同一个文件的顶层位置,不能嵌套在其他类中,这也是被密封类底层实现机制所限制的。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们