views:

92

answers:

2

I found an error in my scala code, which puzzles me. Below is a simplified version of the problem.

In the constructor of an abstract class, I want to check a few asserts about the abstract methods. Thus when an object of a subclass is made, these asserts are checked, to see if all is implemented as it should.

It goes wrong when the subclass implements an abstract method using a "val" however:

Scala code:

abstract class A {
    def aval : String
    assert(aval != null, "aval == null")
    assert(aval == "B", "aval: "+aval)
}

class B extends A {
    def aval = "B"
}

class C extends A {
    val aval = "B"
}

object VariousScalaTests {
    def main(args : Array[String]) : Unit = {
        val b = new B
        val c = new C
    }
}

Scala Error:

Exception in thread "main" java.lang.AssertionError: assertion failed: aval == null
    at scala.Predef$.assert(Predef.scala:92)
    at A.<init>(VariousScalaTests.scala:4)
    at C.<init>(VariousScalaTests.scala:12)
    at VariousScalaTests$.main(VariousScalaTests.scala:19)
    at VariousScalaTests.main(VariousScalaTests.scala)

So it fails at the last line of code: "val c = new C". Class B works perfectly, but class C doesn't! The only difference is that C implements aval using "val" and B using "def".

So my question, most of all, why this difference? I don't understand what's going on.

And is there a way to make it work as I want in both cases in scala? Or am I just missing a more elegant way to assert what I want in scala?

+5  A: 

This equivalent Java code should explain the problem:

public abstract class A {
    public String aval();
}

public class B extends A {
    public String aval() {
        return "B";
    }
}

public class C extends A {
    private String _aval;

    public C() {
        _aval = "B";
    }

    public String aval() {
        return _aval;
    }
}

When you run

val c = new C

the constructor of A runs before the constructor of C and the _aval field isn't assigned yet. So the aval() method returns null (the initial value of the _aval field). But in

val b = new B

there is no such problem.

Generally, you should try to avoid calling virtual methods from a constructor.

And is there a way to make it work as I want in both cases in scala?

See this question for some approaches.

Alexey Romanov
Your java code explains nicely! I see now...Considering behaviour like this, "avoid calling virtual methods from a constructor" seems good very advice. Certainly in scala, as in the java code it's much more clear what's going on.I'll search another way to elegantly check subclasses in my specific case, your link might help.At least the mystery of this error is gone now ;-)
Coder Nr 23
+6  A: 

In Scala you can use early definitions feature to cause subclass's val initialized before super constructor is called:

class C extends {
  val aval = "B"
} with A
venechka
This feature is called Preinitialized Fields, right?
missingfaktor
Not sure. In scala lang spec section 5.1.6 it is called "Early definitions".
venechka