延迟初始化:

假设当我们的类中有全局变量 adapter ,而且该全局变量需要在 onCreate() 中初始化,因此需要先将adapter 定义为null,同时将类名声明为 ? 。我们想在onClick() 中使用adapter 需要保证它在onCreate() 函数调用之后在调用,但是在onClick() 方法中仍需要进行判空处理,否则编译不通过。当全局变量少时,可以通过添加判空符号进行处理,但是随着全局变量增多,会需要编写大量的判空处理代码,而仅为了满足编译器要求。代码如下:

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)
        ...
    }
}

这种情况下可以使用延迟初始化,使用关键字 lateinit ,lateinit 作用时告诉编译器,我会在晚些时候对这个变量进行初始化,这样就不用在一开始的时候将它赋值为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)
        ...
    }
}

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

所以,当你对一个全局变量使用了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是否已经被初始化。这是固定写法!!!!然后取反,如果没有初始化,就进行初始化操作。

密封类

定义一个接口,用于表示某个操作的执行结果。接口不编写内容,然后定义了两个类去实现Result接口:一个Success类用于表示成功时的结果,一个Failure类用于表示失败时的结果。

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

定义一个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的密封类可以很好地解决这个问题,密封类的关键字 为 sealed class,可将接口转化为密封类,写法如下:

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

这样编写 getResultMsg方法时,就不需要else条件了:

fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> "Error is ${result.error.message}"
}

当在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类所对应的条件全部处理。这样就可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的情况。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐