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
我刚刚开始使用并开始使用抽象 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 时,底层 classObject
的支持字段定义为 JVMstatic
个字段。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