Kotlin 覆盖抽象 val 行为,对象 vs class

Kotlin override abstract val behavior, object vs class

我刚刚开始使用并开始使用抽象 classes,覆盖 val 和 singelton。但是,我刚刚遇到了一个非常奇怪的行为。我的目标是拥有一个抽象 class,然后创建几个扩展该抽象 class 的单体。因为我想要求某些变量,所以我创建了抽象 val,然后可以在 subclasses 中覆盖它们(而不是通过构造函数传递它们)。

所以我得到了 4 classes:

主要活动:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val instance = Instance()
        Log.d("MainActivity", "instance randObject: ${instance.randObject}")
        Log.d("MainActivity", "instance randObject: ${instance.randObject.myProp}")
        Log.d("MainActivity", "instance randObject: ${instance.randObject.myProp2}")

        Log.d("MainActivity", "singleton randObject: ${Object.randObject}")
        Log.d("MainActivity", "singleton randObject: ${Object.randObject.myProp}")
        Log.d("MainActivity", "singleton randObject: ${Object.randObject.myProp2}")    
    }
}

实例:

class Instance: AClass(){
    override val testString: String = "test"
    override val testUriString: String = "https://www.google.se"
    override val testUri: Uri = Uri.parse(testUriString)!!
    override val randObject: RandomObject = RandomObject("Herp")
}

对象

object Object : AClass(){
    override val testString: String = "test"
    override val testUriString: String = "https://www.google.se"
    override val testUri: Uri = Uri.parse(testUriString)!!
    override val randObject: RandomObject = RandomObject("Herp")
}

A类:

abstract class AClass{
    abstract val testString: String
    abstract val testUriString: String
    abstract val testUri: Uri
    abstract val randObject: RandomObject

    init {
        Log.d("AClass", "testString: $testString")
        Log.d("AClass", "testUriString: $testUriString")
        Log.d("AClass", "testUri: $testUri")
        Log.d("AClass", "randObject: $randObject")
    }
}

输出:

D/AClass: testString: null
D/AClass: testUriString: null
D/AClass: testUri: null
D/AClass: randObject: null
D/MainActivity: instance randObject: com.technocreatives.abstracttest.RandomObject@4455b26
D/MainActivity: instance randObject: derp
D/MainActivity: instance randObject: Herp

D/AClass: testString: test
D/AClass: testUriString: https://www.google.se
D/AClass: testUri: null
D/AClass: randObject: null
D/MainActivity: singleton randObject: com.technocreatives.abstracttest.RandomObject@8b19367
D/MainActivity: singleton randObject: derp
D/MainActivity: singleton randObject: Herp

在此之后我意识到重写可能直到 init{} 执行后才被初始化。但是后来我看到了创建单身人士时发生的事情。在 init 中设置值 testUriString。为什么会这样?这是一个错误吗? singleton 和 override val 的预期行为是什么?

我尝试搜索文档,但未在文档中找到任何相关信息。

您观察到的行为差异是由 classes 和对象中属性的支持字段的生成方式以及它们的初始化方式引起的。

  • 当 class 使用支持字段覆盖 属性 时,在幕后,派生的 class 中有一个单独的实例字段,并且覆盖了 getter returns 该字段的值。

    因此,当您从超级 class 构造函数内部访问 属性 时,会调用重写的 getter,returns null 字段的值(此时未初始化,因为超级构造函数在 class' 自己的初始化逻辑之前被调用)。

  • 相反,当您定义 object 覆盖 class 时,底层 class Object 的支持字段定义为 JVM static 个字段。

    Object class 也有一个实例(它甚至可以在 Java 中作为 Object.INSTANCE 访问),并且这个实例在某个时刻被初始化时间并调用超级构造函数。

    现在有趣的部分是:当 JVM 加载 class Object class 时,其由常量值初始化的静态字段已经包含这些值,甚至在之前Object<clinit> 中的 PUTSTATIC 指令被执行。

    如果您将 testString 初始化器更改为非常量值,则它不会在访问时被初始化,例如override val testString: String = "test".also { println(it) }

    Here's a gist 这样一个单例的字节码加上我做的几个标记。 请注意,在将值放入 Object<clinit> 之前,抽象 class 的构造函数会访问该字段。

我不确定这是否真的是一个错误,但至少行为不一致。我已经向问题跟踪器报告了这种不一致:https://youtrack.jetbrains.com/issue/KT-21764