Kotlin 覆盖抽象 val 行为、对象与类

Raw*_*awa 6 singleton android object abstract kotlin

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

所以我有 4 节课:

主要活动:

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}")    
    }
}
Run Code Online (Sandbox Code Playgroud)

实例:

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")
}
Run Code Online (Sandbox Code Playgroud)

目的

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")
}
Run Code Online (Sandbox Code Playgroud)

一类:

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

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

我尝试搜索文档,但在文档中没有找到任何关于此的信息。

hot*_*key 5

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

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

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

  • 相反,当您定义object覆盖类时,底层类Object的支持字段定义为 JVMstatic字段。

    Object班有一个实例,以及(它甚至可以在Java中被访问的Object.INSTANCE),而这个实例在某个时间点进行初始化,并调用超构造函数。

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

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

    这是一个带有这样一个单例的字节码的要点,其中有一些我做的标记。请注意,在将值放入<clinit>of之前,抽象类的构造函数会访问该字段Object

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