Object中未初始化的字段

时间:2016-04-24 15:42:51

标签: scala initialization lazy-evaluation

我正在修复一个非常奇怪的错误,其中对象字段中的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。 null为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]]()。这并不是非常有用,因为我知道价值没有初始化,问题是我不知道为什么。由于它是最终的val,我认为它应该是初始化的第一个值之一,特别是在getInstance恰好被调用之前。

我知道我可以使值懒得修复它,但这会引入不必要的性能损失。更重要的是,等效的Java版本private static final ConcurrentHashMap<DateTimeZone, GregorianChronology[]> cCache = new ConcurrentHashMap<DateTimeZone, GregorianChronology[]>()可以正常工作。

1 个答案:

答案 0 :(得分:3)

问题在于:

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

解决方案很简单,只需将sort(>)的初始化移动到对象的顶部,因为它没有引用任何其他内容。这样它将始终首先被初始化。