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?
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With