2016-04-24 116 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