lateinit 延迟初始化

一般情况下,Kotlin 的属性要求我们在声明时必须初始化给它一个值。但是有些时候,我们可能在生命周期还未执行的时候,无法获取到必要的信息,无法完成初始化。此时我们必须要将这个属性声明成 可空类型 ,然后初始化为 null 。等到执行到对应的生命周期获取到必要信息以后,对其再次进行赋值。但是在后面的生命周期中,虽然我们已经明确对这个属性进行了赋值,但是因为它是个 可空类型 ,所以每次我们访问这个属性时还都需要进行判空处理,这无疑是很让人难受的。具体的代码场景如下:

  1. class MainActivty: Activity {
  2. private var prop: String? = null
  3. override fun onCreate(savedInsatnceBundle: Bundle?) {
  4. val param = intent.getStringExtra("param")
  5. prop = param
  6. }
  7. override fun onResume() {
  8. // 使用时
  9. if(prop != null) print(prop)
  10. // 或者使用 ?
  11. print(prop?.length)
  12. }
  13. }

为了解决这个问题,Kotlin 提供了 lateinit 关键字。使用 lateinit 关键字声明想要延迟初始化的属性,就可以在声明属性时,使用不可空类型并不用初始化为 null ,然后在使用属性时,就可以避免编写这种判空的逻辑了。具体的代码如下:

  1. class MainActivty: Activity {
  2. private lateinit var prop: String
  3. override fun onCreate(savedInsatnceBundle: Bundle?) {
  4. val param = intent.getStringExtra("param")
  5. prop = param // 完成初始化
  6. }
  7. override fun onResume() {
  8. // 使用时不必判空
  9. print(prop.length)
  10. }
  11. }

虽然这样会让我们的代码写起来更加的方便,但是也对开发者提出了要求:一定要保证在使用属性前对属性完成了初始化赋值!如果没有初始化就直接使用了属性,那么会抛出 UninitalizedPropertyAccessException 异常。

当然如果我们想要编写一些防御性代码,避免抛出这样的异常引发程序崩溃的话,我们可以通过下面的代码进行判断:

  1. class MainActivty: Activity {
  2. private lateinit var prop: String
  3. override fun onCreate(savedInsatnceBundle: Bundle?) {
  4. val param = intent.getStringExtra("param")
  5. prop = param // 完成初始化
  6. }
  7. override fun onResume() {
  8. // 使用时的防御性判断,看是否完成了初始化
  9. if(::prop.isInitialized) {
  10. print(prop.length)
  11. }
  12. }
  13. }

密封类

密封类是 Java 中没有的一个概念,Kotlin使用 sealed 关键字来修饰一个类,这个类就被称为密封类,一般会与Kotlin的 when 代码块搭配使用。

常见的使用场景如下:

  1. sealed class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
  2. class LeftViewHolder(itemView: View): MyViewHolder(itemView) {
  3. // ...
  4. }
  5. class RightViewHolder(itemView: View): MyViewHolder(itemView) {
  6. // ...
  7. }
  8. class MyAdapter(list: List<Any>): RecyclerView.Adapter<MyViewHolder> {
  9. // ...
  10. override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
  11. val data = list[position]
  12. when(holder) {
  13. is LeftViewHolder -> holder.left.text = data
  14. is RightViewHolder -> holder.right.text = data
  15. }
  16. }
  17. }

可以看到,我们定义了一个 RecyclerView.ViewHolder 的子类作为密封类。然后继承这个密封类,创建了两个子类 LeftViewHolderRightViewHolder 。当我们在 RecyclerView.Adapter 中绑定 ViewHolder 的数据时,通过 when 来判断当前的 holder 是哪一种 ViewHolder,然后调用它们中绑定数据的方法。

此时,如果我们增加了一种ViewHolder,叫做 CenterViewHolder 。如果是在 Java 中,我们需要在定义完 CenterViewHolder 类之后,自己寻找需要更改代码的地方(也就是 when 代码块)。但是使用了密封类的话,如果增加了密封类的子类,那么如果 when 代码块的分支条件如果没有将密封类的子类覆盖完全,那么IDE就会报错。更改后的代码如下:

  1. class CenterViewHolder(itemView: View): MyViewHolder(itemView) {
  2. // ...
  3. }
  4. class MyAdapter(list: List<Any>): RecyclerView.Adapter<MyViewHolder> {
  5. // ...
  6. override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
  7. val data = list[position]
  8. when(holder) {
  9. is LeftViewHolder -> holder.left.text = data
  10. is RightViewHolder -> holder.right.text = data
  11. // 不加这一个分支,when代码块会报错
  12. is CenterViewHolder -> holder.center.text = data
  13. }
  14. }
  15. }

另外再多说一句,密封类及其子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类的底层实现所限制的。