The accepted answer is correct, but bear in mind that the suggested pattern is strange and could lead to hard to understand bugs in non-trivial cases. In my experience, overriding non-abstract vals
will only get you in trouble.
The problem is that the initialisation code is part of the constructor of the defined class/trait. This means that the code initialising both T2.f1
and T3.f1
will be executed when creating an instance of C2
:
trait T2 {
val f1: String = {
println("T2")
"T2f1"
}
}
trait T3 {
val f1: String = {
println("T3")
"T3f1"
}
}
class C2 extends T2 with T3 {
override val f1: String = {
println("C2")
"T3f1"
}
}
new C2 // Will print "T2", then "T3", then "C2"
If the initialisation code has any important side-effect, this might result in hard-to-trackdown bugs! It also has the disadvantage of forcing you to repeat some of T3
's code in C2
.
If you don't absolutely need T2.f1
and T3.f1
to be vals
, you might be better off using defs
to avoid initialisation code in abstract vals
:
trait T2 {
def f1: String = "T2f1"
}
trait T3 {
def f1: String = "T3f1"
}
class C2 extends T2 with T3 {
override val f1: String = "C2f1"
}
In case you really need f1
to be a val, e.g. if you need a stable value to use it in pattern matching statements, you can use the following:
trait T2 {
val f1: String
protected def computeF1: String = {
println("T2")
"T2f1"
}
}
trait T3 {
val f1: String
protected def computeF1: String = {
println("T3")
"T3f1"
}
}
class C2 extends T2 with T3 {
override val f1: String = computeF1
override protected def computeF1: String = super[T3].computeF1
}
new C2
This last solution is a bit more verbose but it bypasses the problem altogether by avoiding the overriding of a non-abstract val
.
class C2 extends T2 with T3 {override val f1: String = ...}
– mfirry