I am currently fixing a very odd error where a private final val
inside an object fields aren't initialised before they are accessed. The location of the code can be found at https://github.com/mdedetrich/soda-time/blob/master/jvm/src/main/scala/org/joda/time/chrono/GregorianChronology.scala#L12-L33.
You can simulate this error by pulling the above repo and then running sodatimeJVM/console
and then in console running ` import org.joda.time._; DateTime.now().minusDays(10)
The code has been posted here
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)
The last line, i.e. var chronos: Array[GregorianChronology] = cCache.get(_zone)
throws a java.lang.NullPointerException
. The value that is null is cCache
however this doesn't make sense since its clearly being initialized at private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
. If I turn on "-Xcheckinit"
Scala then tells me scala.UninitializedFieldError: Uninitialized field: GregorianChronology.scala: 19
which points to private final val cCache = new ConcurrentHashMap[DateTimeZone, Array[GregorianChronology]]()
. This isn't very useful as I know that the value isn't initialized, the problem is that I don't know why. Since its a final val I assume it should be one of the first values that are initialized, especially before getInstance
ever happens to be called.
I know that I can make the value lazy to fix it, that would however introduce an unneeded performance hit. More importantly though, the equivalent Java version private static final ConcurrentHashMap<DateTimeZone, GregorianChronology[]> cCache = new ConcurrentHashMap<DateTimeZone, GregorianChronology[]>()
works absolutely fine.
Null serves as the default value of any uninitialized reference variable, including instance variables and static variables (although you will still receive a compiler warning for uninitialized local variables).
In Java, class and instance variables assume a default value (null, 0, false) if they are not initialized manually. However, local variables aren't given a default value. You can declare but not use an uninitialised local variable. In Java, the default value of any object is null.
A pointer can also be initialized to null using any integer constant expression that evaluates to 0, for example char *a=0; . Such a pointer is a null pointer. It does not point to any object.
You should initialize your variables at the top of the class or withing a method if it is a method-local variable. You can initialize to null if you expect to have a setter method called to initialize a reference from another class. You can, but IMO you should not do that. Instead, initialize with a non-null value.
The problem is here:
private final val INSTANCE_UTC = getInstance(DateTimeZone.UTC)
It calls:
def getInstance(zone: DateTimeZone): GregorianChronology = getInstance(zone, 4)
Which calls:
def getInstance(zone: DateTimeZone, minDaysInFirstWeek: Int): GregorianChronology = {
..
var chronos: Array[GregorianChronology] = cCache.get(_zone)
..
}
But INSTANCE_UTC
is still being initialized, which means we haven't reached cCache
in the initialization order, so cCache
is null
at that point at run time.
This is similar to:
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
}
The solution is simple though, just move the initialization of cCache
to the top of the object, since it doesn't reference anything else. That way it will always be initialized first.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With