在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构造函数如何工作的更多详细信息? 在这两种情况下都不会有相同的行为吗?
关于问题的标题:将方法重写为值是否有害?一些客户端代码可以为库类做到这一点,而不会注意到依赖属性可能会获得无效值,从而产生错误。
答案 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
中的AC
是val
;这样做会强制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