Is it good practice to override abstract methods with values?

In Scala, fields and methods belong to the same namespace, so a field can easily override a method. This is a very attractive feature of Scala. For example, it allows you to bind a class method to a specific reference at some point in the class hierarchy:

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

      

Let's assume there is a second attribute Base

calculated for the 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

      

You might expect the value to v

be 20

, but it is 0

. Ok what happens is that the constructor AC

is called before C

, so x

(which is now an attribute) is not set at that point yet
, and the constructor is using the default for integers (I could be wrong).

What struck me was:

class C extends AC { override def x = 10 }

val v = (new C).y

      

v

is equal to 20.

I have a hunch that the difference is that methods are solved differently than values. Can anyone explain the difference in behavior? more on how Scala constructors work? Wouldn't it be more consistent to have the same behavior in both cases?

As for the title of the question: Isn't it dangerous to override methods as values? Some client code might have done this for a library class without noticing that dependent attributes might get invalid values, thus generating errors.

+3


source to share


1 answer


Superclass fields are created before subclass fields. Thus,

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

class C extends AC {
  override val x = 10
}

      

new C

will first create fields AC

, in this case y

, and then fields C

, i.e. x

... Since the Int

values 0

before initialization, there y = x * 2

will be 0

.

Now, if you override x

as a method ( def

), then C

it has no initialized fields and y = x * 2

calls the method x

that returns 10

, therefore y

takes on a value 20

. Update: To understand, methods do not belong to the same instance, but are shared between all instances of the same class. Therefore, the method x

is not created at compile time when the instance is created C

.



What I find dangerous is the fact that y

in AC

there val

; this results in instantiations y

before the subclass fields are initialized, forming a potential forward link. If you want to refrain from implementing y

as a method, you can implement it as lazy val

, that is, it is evaluated exactly once, namely the first time it is used, in this case only after the object is completely created.

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 val

as 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

      

+2


source







All Articles