Принятый ответ правильный, но имейте в виду, что предлагаемый шаблон странный и может привести к труднопонятным ошибкам в нетривиальных случаях. По моему опыту, переопределение неабстрактного 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"
Если код инициализации имеет какой-либо важный побочный эффект, это может привести к трудно отслеживаемым ошибкам! Он также имеет тот недостаток, что вам приходится повторять часть кода T3
в C2
.
.
Если вам не обязательно, чтобы T2.f1
и T3.f1
были vals
, возможно, вам лучше использовать defs
, чтобы избежать абстрактного кода инициализации vals
:<2794693>179379
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
.
class C2 extends T2 with T3 {override val f1: String = ...}