在Scala Traits和匿名类中使用警卫

时间:2017-06-19 18:21:47

标签: scala

我们假设我们有以下层次结构;超类型具有许多类似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的抽象类中使用保护的推荐方法是什么?

2 个答案:

答案 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点:首先执行超类型(类和特征)的构造函数。因此断言在子类'字段可以初始化之前执行。