子类的Kotlin变量初始化对于初始化值为0的变量的行为很奇怪


16

我创建了以下类层次结构:

open class A {
    init {
        f()
    }

    open fun f() {
        println("In A f")
    }
}

class B : A() {
    var x: Int = 33

    init {
        println("x: " + x)
    }

    override fun f() {
        x = 1
        println("x in f: "+ x)
    }

    init {
        println("x2: " + x)
    }
}

fun main() {
    println("Hello World!!")
    val b = B()
    println("in main x : " + b.x)
}

此代码的输出是

Hello World!!
x in f: 1
x: 33
x2: 33
in main x : 33

但是如果我更改xfrom 的初始化

var x: Int = 33

var x: Int = 0

与上面的输出相反,输出显示了方法的调用:

Hello World!!
x in f: 1
x: 1
x2: 1
in main x : 1

有谁知道为什么用进行初始化0会导致行为不同于使用其他值进行初始化的行为?


4
没有直接关系,但是从构造函数中调用可重写方法通常不是一个好习惯,因为它可能导致意外行为(并有效地破坏子类的超类协定/不变量)。
AdamHošek

Answers:


18

在子类之前初始化超类。

B的构造函数调用调用A的构造函数,后者调用函数f打印“ x in f:1”,在初始化A之后,将初始化B的其余部分。

因此,实质上,该值的设置已被覆盖。

(当您在Kotlin中使用零值初始化图元时,从技术上讲,它们根本就不会初始化)

您可以通过更改签名来观察这种“覆盖”行为

var x: Int = 0var x: Int? = 0

由于x不再是原始类型int,因此实际上将该字段初始化为一个值,从而产生输出:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0

5
当您在Kotlin中使用零值初始化基元时,从技术上讲,它们根本不初始化是我想要读取的内容……谢谢!
deHaar

这似乎仍然是一个错误/不一致。
Kroppeb

2
@Kroppeb这只是Java,仅在Java代码中就可以观察到相同的行为。它与Kotlin没有关系
Sxtanna

8

该行为在文档中进行了描述-https: //kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

如果在基类初始化逻辑中使用了那些属性中的任何一个(直接或间接地通过另一个重写的开放成员实现),则可能导致错误的行为或运行时失败。因此,在设计基类时,应避免在构造函数,属性初始化程序和init块中使用开放成员。

UPD:

有一个错误会导致这种不一致— https://youtrack.jetbrains.com/issue/KT-15642

如果将属性分配为超级构造函数内部虚函数调用的副作用,则如果初始值设定项表达式为默认类型(空,原始零),则其初始值设定项不会覆盖该属性。


1
此外,IntelliJ会警告您。调用f()initA将发出警告“在构造函数中调用非最终函数f”
Kroppeb

在您提供的文档中,它说“基类初始化是第一步,因此要在派生类的初始化逻辑运行之前发生”,这正是问题中第一个示例所发生的。但是,在第二个示例var x: Int = 0中,派生类的初始化指令()根本没有运行,这与文档中所说的相反,这使我相信这可能是一个错误。
Subaru Tashiro

@SubaruTashiro是的,您是对的。这是另一个问题– youtrack.jetbrains.com/issue/KT-15642
vanyochek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.