在初始化超类的继承者时,在其init块中使用抽象val函数时,意外的NPE出现在我的应用程序中,这让我感到困惑.或许有人能解释一下为什么会这样.仅供参考,我通过使用抽象函数暂时解决了这个问题,但我仍然不明白为什么会发生这种情况.

我的超类只是包装了另一个组件,以便更好地表示状态.在init函数中有一个公共的使能函数,它可以导致立即回调,该回调将访问继承类中设置的抽象函数.这会导致NPE,我不知道为什么,因为继承类中的值被正确地覆盖了.以下是代码以及对该问题的一些解释注释:

abstract class SomeSuperClass(private val foundation: SomeFoundation) {

  
  private val callback = object : SomeCallback() {
    override fun onAvailable(network: Network) {
      super.onAvailable(network)
      onAvailable() // Accesses the inheritor which can cause an NPE on init
    }

    override fun onLost(network: Network) {
      super.onLost(network)
      onLost()
    }
  }

  init {
    val manager: SomeManager = SomeManager()
    manager.registerCallback(callback) // Can cause an immediate callback, this is probably why the NPE happens rarely, since it does not always cause an immediate callback.
  }

  abstract val onAvailable: () -> Unit

  abstract val onLost: () -> Unit
}
/** Singleton inheritor. */
class SomeInheritingObject private constructor(): SomeSuperClass(foundation  = SomeFoundation()) {
  private val _state: MutableStateFlow<State> = MutableStateFlow(State.Initial)

  val state: StateFlow<State> = _state

  // This overriden val is not allocated when super.init is called, why?
  override val onAvailable: () -> Unit = {
    _state.value = State.Available
  }

  override val onLost: () -> Unit = {
    _state.value = State.Unavailable
  }

  // This is a singleton component
  companion object {
    private val observer: SomeInheritingObject by lazy { SomeInheritingObject() }

    fun getInstance(): SomeInheritingObject = observer
  }
}

我希望在Super.init中设置被覆盖的抽象函数值,也许它们不是.在这种情况下,如果有人能向我推荐一些文档,我将不胜感激.

推荐答案

// This overriden val is not allocated when super.init is called, why?

你是正确的.当调用super.init时,被重写的val不被初始化.初始化顺序指定为in the spec here:

当使用特定辅助对象初始化分类器类型时 将构造函数ctor委托给主构造函数pctor,在 然后,被委托给相应的超类构造函数sctor, 按照此初始化顺序,会发生以下情况:

  1. 超类对象被初始化,就像通过使用指定的参数调用sctor来创建一样;

  2. 调用接口委托表达式,并将每个表达式的结果存储在对象中,以允许在 委托声明在超类型中的出现顺序 说明符列表;

  3. 使用指定的参数调用PCtor,并按以下顺序初始化由其属性参数声明的所有属性 出现在构造函数声明中;

  4. 类主体中的每个属性初始化代码以及初始化块都是按 班级主体;

  5. 使用指定的参数调用Ctor Body.注意:这意味着如果init块出现在两个属性声明之间 在类体中,它的主体在初始化代码之间调用 这两处房产中.

如果有任何实体,则初始化顺序保持不变 在这种情况下,相应的步骤也被省略 省略(例如,如果对象是使用主构造函数创建的, 不调用第二个的主体).

在您的例子中,没有辅助构造函数或接口委托,因此省略了步骤2和5.不过,关键的一点是,步骤4发生在步骤1之后.超类中的所有内容都首先进行初始化,然后再运行子类中的任何初始化代码,即:

  private val _state: MutableStateFlow<State> = MutableStateFlow(State.Initial)

  val state: StateFlow<State> = _state

  override val onAvailable: () -> Unit = {
    _state.value = State.Available
  }

  override val onLost: () -> Unit = {
    _state.value = State.Unavailable
  }

因此,当您仍然在超类init中时,上面的任何一个都不会被初始化.在JVM上,这意味着它们为空.

我转而使用抽象函数解决了这个问题

我想你的意思是:

  override fun onAvailable() {
    _state.value = State.Available
  }

  override fun onLost() {
    _state.value = State.Unavailable
  }

我不认为这实际上解决了问题,因为如果立即调用回调,_state仍然是空的.

无论如何,我建议您重新设计代码,以避免在子类未完全初始化的地方使用可重写的成员,例如在超类构造函数中.这些成员很可能假设所有成员all都已初始化,就像这里的情况一样.

Kotlin相关问答推荐

判断字符串是否除了.&" ",","@""""

Spring Boot kotlin协程不能并行运行

数据流弹性模板失败,出现错误&未知非复合转换urn";

为什么Kotlin有次构造函数和init块?

Kotlin 函数中接收者和参数的类型相同

如何将glide显示的图像下载到设备存储中.Kotlin

如何从 kotlin 函数中 Select 正确的枚举值

嵌套数组 indexOf()

Fragment的onDestroy()中是否需要将ViewBinding设置为null?

将 Android Studio 升级到 3.1.0 后的 Android Support 插件错误

Intellij 显示 build.gradle.kts 中的每一行都是红色的

Kotlin not nullable值可以为null吗?

Kotlin:子构造函数如何使用其父构造函数的辅助构造函数?

如何将map函数应用于Kotlin中的数组并更改其值?

如何使用协调器布局和行为在CardView上完成此动画?

Kotlin扩展函数与成员函数?

在Kotlin中将列表转换为对的惯用方法

Kotlin中对象和数据类的区别是什么?

Android room DAO 接口不适用于继承

Dagger 2 androidx fragment不兼容类型