Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting a null with a val depending on abstract def in a trait [duplicate]

Tags:

scala

I'm seeing some initialization weirdness when mixing val's and def's in my trait. The situation can be summarized with the following example.

I have a trait which provides an abstract field, let's call it fruit, which should be implemented in child classes. It also uses that field in a val:

scala> class FruitTreeDescriptor(fruit: String) {
     |   def describe = s"This tree has loads of ${fruit}s"
     | }
defined class FruitTreeDescriptor

scala> trait FruitTree {
     |   def fruit: String
     |   val descriptor = new FruitTreeDescriptor(fruit)
     | }
defined trait FruitTree

When overriding fruit with a def, things work as expected:

scala> object AppleTree extends FruitTree {
     |   def fruit = "apple"
     | }
defined object AppleTree

scala> AppleTree.descriptor.describe
res1: String = This tree has loads of apples

However, if I override fruit using a val...

scala> object BananaTree extends FruitTree {
     |   val fruit = "banana"
     | }
defined object BananaTree

scala> BananaTree.descriptor.describe
res2: String = This tree has loads of nulls

What's going on here?

like image 810
jjst Avatar asked Apr 16 '15 11:04

jjst


People also ask

How do I override Val in Scala?

In order to execute a Field Overriding, we need to override variables that are declared utilizing only the val keyword in both super class as well as sub-classes. Field overriding cannot override var, because we can both read as well as write var.

Can Scala trait have variables?

In scala, trait is a collection of abstract and non-abstract methods. You can create trait that can have all abstract methods or some abstract and some non-abstract methods. A variable that is declared either by using val or var keyword in a trait get internally implemented in the class that implements the trait.

Can traits be instantiated?

Classes and objects can extend traits, but traits cannot be instantiated and therefore have no parameters.

Can a trait extend an abstract class?

A class can extend only one abstract class, but it can implement multiple traits, so using traits is more flexible.


1 Answers

In simple terms, at the point you're calling:

val descriptor = new FruitTreeDescriptor(fruit)

the constructor for BananaTree has not been given the chance to run yet. This means the value of fruit is still null, even though it's a val.

This is a subcase of the well-known quirk of the non-declarative initialization of vals, which can be illustrated with a simpler example:

class A {                           
     val x = a
     val a = "String"
}

scala> new A().x
res1: String = null

(Although thankfully, in this particular case, the compiler will detect something being afoot and will present a warning.)

To avoid the problem, declare fruit as a lazy val, which will force evaluation.

like image 138
mikołak Avatar answered Jan 01 '23 21:01

mikołak