对象内的未初始化字段

5
我目前正在修复一个非常奇怪的错误,即在访问对象内部的私有最终值字段之前它们未被初始化。代码位置可在https://github.com/mdedetrich/soda-time/blob/master/jvm/src/main/scala/org/joda/time/chrono/GregorianChronology.scala#L12-L33找到。
您可以通过拉取上述存储库,然后运行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。值为null的是cCache,但这是没有意义的,因为在private final val cCache = new ConcurrentHashMap [DateTimeZone,Array [GregorianChronology]]()处已经被初始化了。如果我打开Scala的"-Xcheckinit",那么Scala告诉我scala.UninitializedFieldError:未初始化字段:GregorianChronology.scala:19,指向private final val cCache = new ConcurrentHashMap[DateTimeZone,Array [GregorianChronology]]()。这并不是很有用,因为我知道该值尚未被初始化,问题是我不知道为什么。由于它是最终的val,我认为它应该是最先初始化的值之一,特别是在调用getInstance之前。
我知道我可以使该值变成lazy来解决它,但这会引入不必要的性能损失。更重要的是,等效的Java版本private static final ConcurrentHashMap<DateTimeZone,GregorianChronology []> cCache = new ConcurrentHashMap<DateTimeZone,GregorianChronology []>()完全正常工作。
1个回答

7
问题出在这里:
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,因此在运行时,此时cCachenull

这类似于:

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的初始化移到对象顶部,因为它没有引用其他内容。这样它就总是会最先被初始化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接