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