用值来覆盖抽象方法,这是一种很好的做法吗?

时间:2015-06-12 17:12:52

标签: scala

在Scala中,字段和方法属于同一名称空间,因此字段可以轻松覆盖方法。这是一个非常有吸引力的Scala功能。例如,它允许在类层次结构的某个点将类方法修复为特定引用:

abstract class AC { def x: Int }
class C extends AC { override val x = 10 }

假设有一个Base的第{{}}个属性,计算方式为x

abstract class AC { def x: Int; val y: Int = x*2 }
class C extends AC { override val x = 10 }

val v = (new C).y

可以预期v值为20,但它为0好的,正在发生的事情是在AC之前调用了C构造函数,因此,x(现在是属性)不是{\ n}设置在那一点,构造函数使用整数的默认值(我可能是错的)。

让我印象深刻的是:

class C extends AC { override def x = 10 }

val v = (new C).y

v等于20。

我有一种预感,不同之处在于方法以不同于值的方式得到解决。有人可以解释行为差异吗?有关Scala构造函数如何工作的更多详细信息? 在这两种情况下都不会有相同的行为吗?

关于问题的标题:将方法重写为值是否有害?一些客户端代码可以为库类做到这一点,而不会注意到依赖属性可能会获得无效值,从而产生错误。

1 个答案:

答案 0 :(得分:2)

超类的字段在子类的字段之前被实例化。所以给出了

abstract class AC {
  def x: Int
  val y: Int = x * 2
}

class C extends AC {
  override val x = 10
}

new C将首先实例化AC的字段,在本例中为y,然后是C的字段,即x。由于Int值在初始化之前为0,因此y = x * 2将为0

现在,如果您将x覆盖为方法(def),则C没有要初始化的字段,而y = x * 2会调用方法x返回10,因此y获取值20更新:为清楚起见,方法不属于单个实例,而是在同一个类的所有实例之间共享。因此,方法x是在编译时创建的,而不是在创建C实例时创建的。

我认为危险的是y中的ACval;这样做会强制y在初始化子类的字段之前进行实例化,从而形成潜在的前向引用。如果您想避免将y实现为方法,则可以将其实现为lazy val,这意味着它只被评估一次,即在第一次使用时,在这种情况下仅在对象完全使用之后实例

abstract class AC {
  def x: Int
  lazy val y: Int = x * 2
}

class C extends AC {
  override val x = 10
}

println((new C).y) // prints 20, because y is initialized on its first use

顺便说一句:如果您将val覆盖为val,则会出现同样的问题。

class AB {
  val x = 10
  val y = x * 2
}
class B extends AB {
  override val x = 4
}

println((new B).y) // prints 0, because the overridden `x` is not yet initialized