Vince.Bdn Vince.Bdn - 1 year ago 144
Scala Question

Scala: constructor orders in inheritance

Can someone explain me in detail about orders in which constructors are called in inheritance in scala please? Say I have:

abstract class A {
private var data: T = compute()
protected def compute(): T
}

class ImpA extends A {
var a = 0
override def compute() {
a = 1
null.asInstanceOf[T] // doesn't matter
}
}

val inst = new ImpA


Then it appears that
inst.a == 0
, so I guess that what happens is that when
ImpA
's constructor is called then,
A
constructor is called also, which actually triggers
compute()
that should set
a = 1
. But then scala goes back down to
ImpA
's constructor and reset
a = 0
. Is that it?

Is there some well-known pattern to avoid this properly? (I'm not really trying to fix this problem that can be easily dealt with, though if there are patterns that are advised I'm eager to know them; but I'd rather like to have a deep understanding of what's happening, and hopefully know why reinitializing the variable
a
could be interested in that case. And also what would happen internally if it was a
val
as it would lead to assign several references to the same variables if the logic is kept...).

Thanks in advance.

EDIT: something fun also is when you do:

abstract class A {
private var data: T = compute()
protected def compute(): T
}

class ImpA extends A {
class B {
var b = 0
}
val b = new B
override def compute() {
b.b += 1
null.asInstanceOf[T] // doesn't matter
}
}

val inst = new ImpA


then it throws a
java.lang.NullPointerException
because
b
isn't instantiated yet.

Answer Source

Let's see what the compiler generates when compiling (using the -Xprint:jvm flag):

class ImpA extends com.testing.A {
  private[this] var a: Int = _;
  <accessor> def a(): Int = ImpA.this.a;
  <accessor> def a_=(x$1: Int): Unit = ImpA.this.a = x$1;
  override def compute(): String = {
    ImpA.this.a_=(1);
    (null: String)
  };
  override <bridge> <artifact> def compute(): Object = ImpA.this.compute();
  def <init>(): com.testing.ImpA = {
    ImpA.super.<init>();
    ImpA.this.a = 0;
    ()
  }
};

What do we see? We see that the constructor running for ImplA (defined as the <init> method) first calls ImpA.super.<init>(), which is a call to A to initialize itself first. As initialization code looks like this:

def <init>(): com.testing.A = {
  A.super.<init>();
  A.this.data = A.this.compute();
  ()
}

It calls A.super, which is Object, and then calls A.this.compute(). This method initializes a to hold the value 1. Once that initialization finishes, ImplA sets a to 0, as you told it to do during constructor initialization. That is why you're seeing the value 0 for a.

To sum up, the execution flow is as follows:

  1. ImplA calls As init method
  2. A calls compute, which is invoked on ImplA
  3. ImplA.compute assigns a the value 1
  4. ImplA assigns a the value 0
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download