Overriding abstract methods with values, a good practice?

In Scala, fields and methods belong to the same namespace so a field can easily override a method. That's a very attractive Scala feature. For example, it allows fixing a class method to an specific reference at some point of a classes hierarchy:

abstract class AC { def x: Int }
class C extends AC { override val x = 10 }

Suppose there is a second Base's attribute calculated form x:

abstract class AC { def x: Int; val y: Int = x*2 }
class C extends AC { override val x = 10 }

val v = (new C).y

One could expect v value to be 20 however it is 0. Ok, what is happening is that AC constructor is being called before C's, hence, x (which is now an attribute) isn't set at that point yet and the constructor uses the default value for integers (I may be wrong).

What strikes me is that with:

class C extends AC { override def x = 10 }

val v = (new C).y

v equals 20.

I have a hunch that the difference comes from the fact that methods get resolved in a different way than values. Can somebody give an explanation for the behaviour difference? more details on how Scala constructors work? Wouldn't it be more coherent to have the same behaviour in both cases?

Concerning the question's title: Isn't it hazardous to override methods as values? Some client code could do that for a library class without noticing that dependant attributes could get invalid values thus generating bugs.

Upvotes: 3

Views: 96

Answers (1)

Kulu Limpa
Kulu Limpa

Reputation: 3541

The fields of the superclass are instantiated before the fields of the subclass. So given

abstract class AC {
  def x: Int
  val y: Int = x * 2
}

class C extends AC {
  override val x = 10
}

new C will first instantiate the fields of AC, in this case y and then the fields of C, i.e., x. Because Int values are 0 before initialization, y = x * 2 will be 0.

Now, if you override x as a method (def), then C has no fields to be initialized and y = x * 2 calls the method x which returns 10, hence y takes the value 20. Update: To make it clear, methods do not belong to a single instance, but are shared among all instances of the same class. Hence, the method x is created at compile time not when the instance of C is created.

The thing I consider dangerous here is that y in AC is a val; doing so forces y to be instantiated before the fields of the subclasses are initialized, forming a potential forward reference. If you want to refrain from implementing y as a method, you can implement it as a lazy val, meaning it is evaluated exactly once, namely on its first use, in this case only after the object is fully instantiated.

abstract class AC {
  def x: Int
  lazy val y: Int = x * 2
}

class C extends AC {
  override val x = 10
}

println((new C).y) // prints 20, because y is initialized on its first use

By the way: The same problem occurs if you override a val as a val.

class AB {
  val x = 10
  val y = x * 2
}
class B extends AB {
  override val x = 4
}

println((new B).y) // prints 0, because the overridden `x` is not yet initialized

Upvotes: 2

Related Questions