Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala abstract method is null in superclass when subclass implements it using val?

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?

like image 378
Coder Nr 23 Avatar asked Jul 28 '10 13:07

Coder Nr 23


2 Answers

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
like image 124
venechka Avatar answered Oct 04 '22 01:10

venechka


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.

like image 24
Alexey Romanov Avatar answered Oct 04 '22 00:10

Alexey Romanov