2016-04-24 105 views
3

我目前正在修复对象字段内的private final val在被访问前未初始化的非常奇怪的错误。代码的位置可以在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。该值为空是cCache但是这是没有意义的,因为它清楚地被初始化为private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()。如果我打开"-Xcheckinit"斯卡拉然后告诉我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[]>()工作得很好。

回答

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中的初始化顺序,所以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初始化对象的顶部,因为它不引用任何东西。这样它将始终首先被初始化。

+0

谢谢,感觉如此愚蠢的错过! – mdedetrich