接受的答案是正确的,但请记住,建议的模式很奇怪,并且可能导致在非平凡情况下难以理解的错误。根据我的经验,覆盖非抽象的 vals 只会给你带来麻烦。
问题在于初始化代码是定义的类/特征的构造函数的一部分。这意味着初始化T2.f1和T3.f1的代码将在创建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"
如果初始化代码有任何重要的副作用,这可能会导致难以追踪的错误!它还有一个缺点,就是强迫你在C2中重复T3的一些代码。
如果你不是绝对需要T2.f1 和T3.f1 成为vals,你最好使用defs 来避免抽象vals 中的初始化代码:
trait T2 {
def f1: String = "T2f1"
}
trait T3 {
def f1: String = "T3f1"
}
class C2 extends T2 with T3 {
override val f1: String = "C2f1" // You can keep this a def if you like
}
如果你真的需要 f1 是一个 val,例如如果你需要一个稳定的值来在模式匹配语句中使用它,你可以使用以下内容:
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 // You can keep this a def if you like
override protected def computeF1: String = super[T3].computeF1
}
new C2 // Only prints "T3" once
最后一个解决方案有点冗长,但它通过避免覆盖非抽象val 完全绕过了这个问题。