2016-04-24 2 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, то сообщит мне 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 в порядке инициализации, поэтому 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 в верхней части объекта, так как он ничего не ссылаться. Таким образом, он всегда будет инициализирован первым.

+0

Спасибо, чувствую себя настолько глупо, что этого не хватает! – mdedetrich

Смежные вопросы