我们假设我们有以下层次结构;超类型具有许多类似DTO的实现。这些实现必须始终根据某些特定于域的规则有效,因此以assert-statements形式的guard子句被添加到已实现的主体中;
trait AbstractModel {
def a: Int
def b: Int
}
case class ConcreteModel(a: Int, b: Int, c: Int) extends AbstractModel {
assert(a >= 0)
assert(a > b)
assert(c != 0)
}
由于在我们的域中第一个和第二个保护语句必须为超类型的实现,我们宁愿将这些检查移动到这个超类型;
trait AbstractModelWithGuard {
def a: Int
def b: Int
assert(a >= 0, s"Invalid attribute [$a >= 0]")
assert(a > b, s"Invalid attribute [$a > $b]")
}
case class ConcreteModelVariant(a: Int, b: Int, c: Int) extends AbstractModel {
assert(c != 0)
}
一切都很好。然后我们使用匿名类实现超类型(特征未密封);
val x = new AbstractModelWithGuard {
override val a: Int = 1
override val b: Int = -1
}
导致java.lang.AssertionError异常!
java.lang.AssertionError:断言失败:无效的属性[0> 0]
我假设我的trait中的assert-statements在我的匿名对象的某个中间实例中被赋值,然后才会被赋值。我没想到会出现这种情况。
为什么我的断言语句失败,在Scala的抽象类中使用保护的推荐方法是什么?
答案 0 :(得分:2)
特征实例具有复杂的初始化顺序:
scala> trait AbstractModelWithGuard {
| def a: Int
| assert(a > 10, "`a` is not bigger than 10")
| }
scala> val x = new AbstractModelWithGuard { val a = 11 }
java.lang.AssertionError: assertion failed: `a` is not bigger than 10
at scala.Predef$.assert(Predef.scala:170)
一个解决方法是早期初始化语法:
scala> val x = new { val a = 11 } with AbstractModelWithGuard
x: AbstractModelWithGuard{val a: Int} = $anon$1@3bfef1ea
scala> x.a
res1: Int = 11
另一个, lazy vals :
scala> val x = new AbstractModelWithGuard { lazy val a = 11 }
x: AbstractModelWithGuard{lazy val a: Int} = $anon$1@35e8b5e4
scala> x.a
res2: Int = 11
答案 1 :(得分:1)
Pedrofurla的答案已经提供了解决方法,但我更愿意比“复杂的初始化顺序”更详细地解释原因。特别是因为它完全没有特质:
class AbstractModelWithGuard {
val a: Int = 11
assert(a > 10, "`a` is not bigger than 10")
}
val x = new AbstractModelWithGuard { override val a = 11 }
// throws the same AssertionError
第1点:(具体)val
成员对应于JVM术语中的2个“真实”成员:私有字段和getter方法。这是覆盖超类型def
(或上例中的val
)的方法。字段无法覆盖或覆盖。
第2点:特征/类体中的表达式,val
/ var
初始化器构成构造函数。所以你的断言是AbstractModelWithGuard
构造函数的一部分,而a
的初始化是在匿名类的构造函数中。在上面的示例中,AbstractModelWithGuard
的私有字段也在其构造函数中初始化,但断言中的方法a
是覆盖的,因此它将访问匿名类的字段!
第3点:首先执行超类型(类和特征)的构造函数。因此断言在子类'字段可以初始化之前执行。