在基类初始化(直接或间接)中使用覆盖属性的示例是啥?

Posted

技术标签:

【中文标题】在基类初始化(直接或间接)中使用覆盖属性的示例是啥?【英文标题】:What's an example of using an overridden property in the Base Class initialization (either directly or indirectly)?在基类初始化(直接或间接)中使用覆盖属性的示例是什么? 【发布时间】:2021-07-11 08:23:55 【问题描述】:

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

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

说清楚,我理解构造函数和继承之间的数据流。我无法理解的是我们如何在基类初始化中使用被覆盖的属性。它说

可能直接或间接发生

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

另外,它说

因此,您应该避免在构造函数中使用开放成员, 属性初始化器和初始化块。

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

编辑评论:

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()

【问题讨论】:

【参考方案1】:

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

这是一种直接的方式:

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"


编辑:以下是有关 cmets 后续问题的一些说明。

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

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

参数something 传递给Child 的主要构造函数 Child 类上的 属性something,它覆盖了父类的属性 对父构造函数的调用 (Base())

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

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

您可能会在此处混合 声明运行时。尽管我们在Base 类和Child 类中声明事物,但在此示例代码中,运行时只有一个实例(Child 的实例)。

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

如果您看到 Java 等价物,您可能不会那么困惑。很明显,如果您将抽象 something 视为 getter getSomething() 的声明。子类重写这个getSomething() 方法并声明一个私有的something 字段,getter 返回字段something 的当前值。但是该字段仅在父(和 init 块)的构造函数完成执行后初始化。

【讨论】:

我尝试通过调试第一个示例来跟踪数据。它表明,在您到达 init 块后,它会跳回构造函数(或者可能是类)以获取“某物”的值,并且因为它没有被初始化,所以它给出了 null。首先,我想问一下我是否正确理解了流程,如果是,我不太明白为什么 init 块中的代码会用于子类构造函数中的参数。我们甚至没有用参数或任何东西委托基本构造函数,但它仍然适用于进行覆盖的参数。 @JockyCracker 也许你对 Kotlin 的主要构造函数的紧凑语法感到困惑。我编辑了我的答案。 我把术语参数和属性混淆了,mb。我的意思是超类如何能够在 init 块中使用子类的属性。我还尝试了一个非抽象示例,我目前的理解是:我们可以在超类中编写打印“某物”而不会出现任何编译时错误,因为超类本身实际上具有该属性,但是当我们使用“覆盖”时' 在子类中,并创建它的实例,它会覆盖超类中属性的值。这样对吗?我还将添加我创建的用于测试。 @JockyCracker 对,Child 实例只有属性的覆盖定义可用,并且只有一个Child 实例。 init 块作为Child 实例初始化的一部分有效地运行,即使它的代码在其他地方定义。你可以把它想象成init 块本身在子实例中,并且首先声明(这就是为什么它看不到something 的初始值)。

以上是关于在基类初始化(直接或间接)中使用覆盖属性的示例是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在基类中编写需要在子类中覆盖的方法实现?

C#中基类属性值在子类中设置,如何在基类的方法中获取子类设置的值?

如何避免在基类初始化程序中调用默认构造函数?

mypy:基类没有属性x,如何在基类中键入提示

对虚函数进行重载是啥意思?

java的三个基本特征是啥?