什么时候在Scala特性中使用val或def?

我正在浏览有效的scala幻灯片 ,并在第10 张幻灯片中提到,从不使用val作为抽象成员的trait ,而是使用def 。 幻灯片没有详细提及为什么在trait使用抽象的val是一种反模式。 我将不胜感激,如果有人可以解释最佳做法围绕使用val vs def抽象方法的特点

def可以通过defvallazy 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 } 

你将无法定义F1F3


好吧,混淆你并回答@ 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意味着它可以被广泛的实施。 它可以为每个呼叫返回不同的持有者,持有者将包含不同的Innertypes。 但是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