在Kotlin中,访问init
块中的abstract val
会导致NullPointerException
,因为在超级类的init
块执行后,扩展类会覆盖该字段。
理想的解决方案是在对象实例化的所有阶段完成后,声明一些要执行的代码/函数。我只能考虑创建一个initialize()
函数并手动调用它,这很糟糕,因为它不是自动的。将其粘贴在init
块中不起作用,如下例所示。
正如下面的注释所指出的,它们可以作为参数传入,而不是重写字段,但这对我的实际用例不起作用。它为对象构造增加了很多混乱,当其他类试图扩展它时,这是一场噩梦
下面的例子展示了一个使用协同程序的解决方案。正在等待字段!=null在这种情况下有效,但当map是具有默认值的open val
时无效,该默认值可能会被覆盖,也可能不会被覆盖。
这个问题在一定程度上得到了解决,但解决方案远非最佳。任何建议和替代解决方案都将不胜感激。
@Test @Suppress("ControlFlowWithEmptyBody", "SENSELESS_COMPARISON")
fun abstractValAccessInInitNPE() {
val key = "Key"
val value = "Value"
abstract class Mapper {
abstract val map: HashMap<String, String>
fun initialize() { map[key] = value }
}
// Test coroutine solution on abstract mapper
println("CoroutineMapper")
abstract class CoroutineMapper: Mapper() {
init {
GlobalScope.launch {
while (map == null) {}
initialize()
}
}
}
val coroutineMapper = object : CoroutineMapper() {
override val map = HashMap<String, String>()
}
val start = System.nanoTime()
while (coroutineMapper.map.isEmpty()) {} // For some reason map == null doesn't work
println("Overhead: ${(System.nanoTime() - start) / 1000000.0} MS")
println("Mapped: ${coroutineMapper.map[key].equals(value)}")
// Test coroutine solution on open mapper
println("nDefaultMapper")
open class DefaultMapper: Mapper() {
override val map = HashMap<String, String>()
}
val newMap = HashMap<String, String>()
val proof = "Proof"
newMap[proof] = proof
val defaultMapper = object: DefaultMapper() {
override val map = newMap
}
Thread.sleep(1000) // Definitely finished by the end of this
println("Mapped: ${defaultMapper.map[proof].equals(proof) && defaultMapper.map[key].equals(value)}")
// Basic solution (doesn't work)
println("nBrokenMapper")
abstract class BrokenMapper: Mapper() {
init { initialize() } // Throws NPE because map gets overridden after this
}
val brokenMapper = object: BrokenMapper() {
override val map = HashMap<String, String>()
}
println("Mapped: ${brokenMapper.map[key].equals(value)}")
}
永远不应该从构造函数中调用open函数(就像所有抽象函数一样(,因为这样就无法在超类中保证类的初始状态。它可以导致各种非常棘手的错误。
如果你退后一步,通常有一个很好的方法来解决这个问题。例如,与其将映射作为抽象属性,不如将其作为超类中的构造函数参数。在子类构造函数尝试使用它之前,您就知道它已经初始化了
abstract class Mapper(key: String, value: String, val map: HashMap<String, String>)
abstract class DecentMapper(key: String, value: String, map: HashMap<String, String>) : Mapper(key, value, map) {
init {
map[key] = value
}
}
val key = "Key"
val value = "Value"
val decentMapper = object : DecentMapper(key, value, HashMap()){
//...
}