首页 > 解决方案 > 在基类初始化(直接或间接)中使用覆盖属性的示例是什么?

问题描述

这意味着,在基类构造函数执行时,派生类中声明或覆盖的属性尚未初始化。如果在基类初始化逻辑中使用了这些属性中的任何一个(直接或间接地,通过另一个覆盖的开放成员实现),则可能导致不正确的行为或运行时故障。因此,在设计基类时,应避免在构造函数、属性初始值设定项和 init 块中使用开放成员。

我正在从 Kotlin 文档中学习继承,我被困在这里。有另一个帖子问了一个关于这个的问题,但答案正是文档以不同的方式所说的。

它可能直接或间接发生

  这实际上意味着什么?基类如何能以某种方式访问​​派生类中被覆盖的属性?

因此,您应该避免在构造函数、属性初始值设定项和 init 块中使用开放成员。

 那么我们如何才能正确使用开放属性呢?

编辑评论:

fun main ()
{
    val d = Derived("Test2")
}

open class Base()
{
    open val something:String = "Test1"

    init
    {
        println(something)  //prints null
    }
}

class Derived(override val something: String): Base()

标签: kotlin

解决方案


这实际上意味着什么?基类如何能以某种方式访问​​派生类中被覆盖的属性?

这是一种直接的方法:

abstract class Base {
    abstract val something: String

    init {
        println(something)
    }
}

class Child(override val something: String): Base()

fun main() {
    Child("Test") // prints null! because the property is not initialized yet
}

这会打印null,这对于不可为空的String属性来说是非常糟糕的。

因此,您应该避免在构造函数、属性初始值设定项和 init 块中使用开放成员。

那么我们如何才能正确使用开放属性呢?

您可以在基类的常规方法(或自定义属性 getter)中使用这些属性:

abstract class Base {
    
    abstract val something: String

    fun printSomething() {
        println(something)
    }
}

class Child(override val something: String): Base()

fun main() {
    Child("Test").printSomething() // correctly prints "Test"
}

编辑:以下是关于评论中后续问题的一些说明。

我不太明白为什么 init 块中的代码用于子类构造函数中的参数

我认为您可能会对 Kotlin 的主要构造函数的简洁语法感到困惑,这可能会使调试器的流程难以理解。在Child声明中,我们其实声明了很多东西:

  • 传递给的主构造函数的参数 somethingChild
  • 类的属性 somethingChild它覆盖了父类的属性
  • 对父构造函数 ( Base())的调用

Child()被调用时,它立即调用运行该块的Base()无参数构造函数。init

我们甚至没有用参数或任何东西委托基本构造函数,但它仍然用于执行覆盖的参数

您可能会在这里混合声明运行时。虽然我们在类和类中声明了东西,但在这个示例代码中,运行时只有 1 个实例(的实例)。BaseChildChild

所以,实际上,这里只调用了 1 个属性something(内存中只有一个位置)。如果init块访问这个属性,它只能是子实例的属性。我们不需要向Base构造函数传递任何内容,因为该块是使用实例init的数据/字段有效执行的。Child

如果您看到与此等效的 Java,也许您会不那么困惑。如果您将 abstractsomething视为 getter 的声明,那就很明显了getSomething()。子类重写此getSomething()方法并声明一个私有something字段,getter 返回该字段的当前值something。但是该字段仅在父(和 init 块)的构造函数完成执行后才被初始化。


推荐阅读