对象中未初始化的字段
Uninitialized fields inside an Object
我目前正在修复一个非常奇怪的错误,即对象字段中的 private final val
在被访问之前未被初始化。代码的位置可以在https://github.com/mdedetrich/soda-time/blob/master/jvm/src/main/scala/org/joda/time/chrono/GregorianChronology.scala#L12-L33.
找到
你可以通过拉取上面的 repo 来模拟这个错误,然后 运行 sodatimeJVM/console
然后在控制台 运行 ` import org.joda.time._; DateTime.now().minusDays(10)
代码已经贴在这里
object GregorianChronology {
private final val MILLIS_PER_YEAR = (365.2425 * DateTimeConstants.MILLIS_PER_DAY).toLong
private final val MILLIS_PER_MONTH = (365.2425 * DateTimeConstants.MILLIS_PER_DAY / 12).toLong
private final val DAYS_0000_TO_1970 = 719527
private final val MIN_YEAR = -292275054
private final val MAX_YEAR = 292278993
private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)
private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
def getInstanceUTC(): GregorianChronology = INSTANCE_UTC
def getInstance(): GregorianChronology = getInstance(DateTimeZone.getDefault, 4)
def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)
def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
var _zone: DateTimeZone = zone
if (_zone == null) {
_zone = DateTimeZone.getDefault
}
var chrono: GregorianChronology = null
var chronos: Array[GregorianChronology] = cCache.get(_zone)
最后一行,即 var chronos: Array[GregorianChronology] = cCache.get(_zone)
抛出一个 java.lang.NullPointerException
。空值是 cCache
但这没有意义,因为它显然是在 private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
处初始化的。如果我打开 "-Xcheckinit"
Scala 然后告诉我 scala.UninitializedFieldError: Uninitialized field: GregorianChronology.scala: 19
指向 private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
。这不是很有用,因为我知道该值未初始化,问题是我不知道 为什么 。由于它是一个最终值,我认为它应该是第一个被初始化的值,尤其是在 getInstance
被调用之前。
我知道我可以使值懒惰来修复它,但这会引入不必要的性能损失。更重要的是,等效的 Java 版本 private static final ConcurrentHashMap<DateTimeZone, GregorianChronology[]> cCache = new ConcurrentHashMap<DateTimeZone, GregorianChronology[]>()
工作得很好。
问题在这里:
private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)
它调用:
def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)
调用:
def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
..
var chronos: Array[GregorianChronology] = cCache.get(_zone)
..
}
但是INSTANCE_UTC
还在初始化中,这意味着我们在初始化顺序上还没有达到cCache
,所以cCache
在那个时候是null
运行时间。
这类似于:
object Test {
val a = foo("a") // Calls a def which references and uses an uninitialized val, NPE
val b = "b"
def foo(c: String): Int = b.length + c.length
}
虽然解决方案很简单,只需将 cCache
的初始化移动到对象的顶部,因为它不引用任何其他内容。这样它将始终首先初始化。
我目前正在修复一个非常奇怪的错误,即对象字段中的 private final val
在被访问之前未被初始化。代码的位置可以在https://github.com/mdedetrich/soda-time/blob/master/jvm/src/main/scala/org/joda/time/chrono/GregorianChronology.scala#L12-L33.
你可以通过拉取上面的 repo 来模拟这个错误,然后 运行 sodatimeJVM/console
然后在控制台 运行 ` import org.joda.time._; DateTime.now().minusDays(10)
代码已经贴在这里
object GregorianChronology {
private final val MILLIS_PER_YEAR = (365.2425 * DateTimeConstants.MILLIS_PER_DAY).toLong
private final val MILLIS_PER_MONTH = (365.2425 * DateTimeConstants.MILLIS_PER_DAY / 12).toLong
private final val DAYS_0000_TO_1970 = 719527
private final val MIN_YEAR = -292275054
private final val MAX_YEAR = 292278993
private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)
private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
def getInstanceUTC(): GregorianChronology = INSTANCE_UTC
def getInstance(): GregorianChronology = getInstance(DateTimeZone.getDefault, 4)
def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)
def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
var _zone: DateTimeZone = zone
if (_zone == null) {
_zone = DateTimeZone.getDefault
}
var chrono: GregorianChronology = null
var chronos: Array[GregorianChronology] = cCache.get(_zone)
最后一行,即 var chronos: Array[GregorianChronology] = cCache.get(_zone)
抛出一个 java.lang.NullPointerException
。空值是 cCache
但这没有意义,因为它显然是在 private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
处初始化的。如果我打开 "-Xcheckinit"
Scala 然后告诉我 scala.UninitializedFieldError: Uninitialized field: GregorianChronology.scala: 19
指向 private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
。这不是很有用,因为我知道该值未初始化,问题是我不知道 为什么 。由于它是一个最终值,我认为它应该是第一个被初始化的值,尤其是在 getInstance
被调用之前。
我知道我可以使值懒惰来修复它,但这会引入不必要的性能损失。更重要的是,等效的 Java 版本 private static final ConcurrentHashMap<DateTimeZone, GregorianChronology[]> cCache = new ConcurrentHashMap<DateTimeZone, GregorianChronology[]>()
工作得很好。
问题在这里:
private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)
它调用:
def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)
调用:
def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
..
var chronos: Array[GregorianChronology] = cCache.get(_zone)
..
}
但是INSTANCE_UTC
还在初始化中,这意味着我们在初始化顺序上还没有达到cCache
,所以cCache
在那个时候是null
运行时间。
这类似于:
object Test {
val a = foo("a") // Calls a def which references and uses an uninitialized val, NPE
val b = "b"
def foo(c: String): Int = b.length + c.length
}
虽然解决方案很简单,只需将 cCache
的初始化移动到对象的顶部,因为它不引用任何其他内容。这样它将始终首先初始化。