Saturday, April 13, 2013

Scala: when is a val not a val?

TL;DR: before it's initialized.

Do vals in Scala always contain the same value?
Not quite.

I hit this the other day when I used a val inside a def inside a val.

In the superclass:

abstract class GeneralTestCase {
  def createArray = Array(1,2,3,4)
  val defaultInput createArray
// defaultInput = [1,2,3,4]

and then in the subclass, customized behavior to make that default input be empty... but it comes out a little too empty.

class EmptyInputTestCase extends GeneralTestCase {
  val emptyArray = Array.empty[Int] 
  override def createArrayemptyArray
// defaultInput = null

Why is defaultInput null instead of an empty array? Because of initialization order. The bodies of classes are the constructor, and the order of class construction looks like this:

The superclass is initialized first, just as in Java the superclass constructor is called first. It recognizes the method override for createArray. Unfortunately the subclass's implementation of createArray references a subclass field, and the subclass fields are not initialized yet. That comes after superclass construction.

This is a known rule in Java: don't call non-final methods from the constructor, because subclasses can override them and reference uninitialize fields. The same applies in Scala. It's trickier to spot in Scala because the whole class body is the constructor, and references to vals look the same as calls to defs without argument lists.

1 comment: