Star (-) Watch (-)

Scala Tour

Object Init

There are some weird cases in java, where the code looks like it will init properly, but won't. This happens more often in scala and can be hard to understand.

Vals in Traits

Lets define a simple trait that requires a val to be defined. This trait will then use it on construction.

trait Foo {
    val a: Int

    println(s"Value of a is $a")
}

Now, lets create the foo object

new Foo {
    val a: Int = 12

    println(s"In extending type: $a")
}

vs

class FooImpl(val a: Int) extends Foo {
    println(s"In extending type: $a")
}
new FooImpl(12)

vs

class Foo2Impl(val b: Int) extends Foo {
    val a = b

    println(s"In extending type: $a")
}
new Foo2Impl(12)

vs

class Foo3Impl(val b: Int) extends Foo {
    override val a = b

    println(s"In extending type: $a")
}
new Foo3Impl(12)

What do you think the output will be? Lets find out.

scala> trait Foo {
     |     val a: Int
     |
     |     println(s"Value of a is $a")
     | }
defined trait Foo

scala> new Foo {
     |     val a: Int = 12
     |
     |     println(s"In extending type: $a")
     | }
Value of a is 0
In extending type: 12
res0: Foo = $anon$1@661a642b

scala> class FooImpl(val a: Int) extends Foo {
     |     println(s"In extending type: $a")
     | }
defined class FooImpl

scala> new FooImpl(12)
Value of a is 12
In extending type: 12
res1: FooImpl = FooImpl@27a7a9f

scala> class Foo2Impl(val b: Int) extends Foo {
     |     val a = b
     |
     |     println(s"In extending type: $a")
     | }
defined class Foo2Impl

scala> new Foo2Impl(12)
Value of a is 0
In extending type: 12
res2: Foo2Impl = Foo2Impl@70ff4bfb

scala> class Foo3Impl(val b: Int) extends Foo {
     |     override val a = b
     |
     |     println(s"In extending type: $a")
     | }
defined class Foo3Impl

scala> new Foo3Impl(12)
Value of a is 0
In extending type: 12
res3: Foo3Impl = Foo3Impl@2123dde6

So, the output is 0, 12, 0, then 0 again? Why is this? When init is called for a super class/trait the parent constructor is done before the first line of the extending type (but after class definition). Also with override, you would have assumed that the output was 12 12, but it was like the others (0, 12). This is because how the compiler treats override vals: they are only initialized at the call site and not propagated lower. What does this mean? It means that if foo had a default that was non 0, the output would not change!

trait Bar {
    val bar: String = "default"

    println(s"Base: $bar")
}

new Bar {
    override val bar: String = "top"

    println(s"Top: $bar")
}
// output
// Base: null
// Top: top

class Bar1(b: String) extends Bar {
    override val bar: String = b

    println(s"Top: $bar")
}
new Bar1("top")
// output
// Base: null
// Top: top

class Bar2(bar: String) extends Bar {
    println(s"Top: $bar")
}
new Bar2("top")
// output
// Base: default
// Top: top

class Bar3(override val bar: String) extends Bar {
    println(s"Top: $bar")
}
new Bar3("top")
// output
// Base: top
// Top: top

The above is what was meant by the value is defined at the call site but not lower down. When the type overrides a val, only at that level will it be evaluated. The base types will be given a null value (or 0 for numbers). To avoid these kinds of bugs, try to avoid vals in traits; will save you time trying to figure out how a null snuck in.