什么时候在Scala特性中使用val或def?
我正在浏览有效的scala幻灯片 ,并在第10 张幻灯片中提到,从不使用val
作为抽象成员的trait
,而是使用def
。 幻灯片没有详细提及为什么在trait
使用抽象的val
是一种反模式。 我将不胜感激,如果有人可以解释最佳做法围绕使用val vs def抽象方法的特点
def
可以通过def
, val
, lazy val
或者object
。 所以这是定义一个成员的最抽象的forms。 由于特征通常是抽象的接口,所以说你想要一个val
就是说实现应该怎么做。 如果你要求一个val
,一个实现类不能使用def
。
只有当你需要一个稳定的标识符时才需要val
,例如一个path依赖types。 这是你通常不需要的东西。
比较:
trait Foo { def bar: Int } object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok class F2(val bar: Int) extends Foo // ok object F3 extends Foo { lazy val bar = { // ok Thread.sleep(5000) // really heavy number crunching 42 } }
如果你有
trait Foo { val bar: Int }
你将无法定义F1
或F3
。
好吧,混淆你并回答@ om-nom-nom – 使用抽象的val
会导致初始化问题:
trait Foo { val bar: Int val schoko = bar + bar } object Fail extends Foo { val bar = 33 } Fail.schoko // zero!!
这是一个丑陋的问题,我个人认为这个问题应该在将来的Scala版本中通过修正它在编译器中消失,但是,目前这也是为什么不应该使用抽象值的原因。
编辑 (2016年1月):您可以使用lazy val
实现覆盖抽象的val
声明,这样也可以防止初始化失败。
我更喜欢在特征中不使用val
,因为val声明具有不清楚和非直观的初始化顺序。 你可以添加一个特性到已经工作的层次结构,它会打破所有以前工作的东西,请参阅我的主题: 为什么在非final类中使用plain val
记住使用这个val声明的所有事情,最终会导致错误。
更新更复杂的例子
但有时候你无法避免使用val
。 正如@ 0__所提到的,有时你需要一个稳定的标识符,而def
不是一个。
我会举一个例子来说明他在说什么:
trait Holder { type Inner val init : Inner } class Access(val holder : Holder) { val access : holder.Inner = holder.init } trait Access2 { def holder : Holder def access : holder.Inner = holder.init }
此代码会产生错误:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found. def access : holder.Inner =
如果你花一分钟时间想你会明白编译器有抱怨的理由。 在Access2.access
情况下,它不能以任何方式派生返回types。 def holder
意味着它可以被广泛的实施。 它可以为每个呼叫返回不同的持有者,持有者将包含不同的Inner
types。 但是Java虚拟机期望返回相同的types。
总是使用def似乎有点尴尬,因为像这样的东西不会工作:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
你会得到以下错误:
error: value id_= is not a member of Entity