何时在Scala特征中使用val或def?

Man*_*raf 84 inheritance scala traits

我正想通过有效的斯卡拉幻灯片,并提到在幻灯片10至从来不使用valtrait抽象成员和使用def来代替.幻灯片没有详细提及为什么val在a中使用抽象trait是一种反模式.如果有人可以解释在抽象方法的特性中使用val vs def的最佳实践,我将不胜感激

0__*_*0__ 123

A def可以通过a def,a val,a lazy val或a来实现object.所以这是定义成员最抽象的形式.由于性状通常是抽象接口,说你想有一个val是说如何实施应该做的.如果你要求a val,实现类不能使用a def.

val只有在需要稳定标识符时才需要A ,例如,对于路径相关类型.这是你通常不需要的东西.


相比:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

如果你有

trait Foo { val bar: Int }
Run Code Online (Sandbox Code Playgroud)

你将无法定义F1F3.


好的,混淆你并回答@ om-nom-nom-using abstract vals会导致初始化问题:

trait Foo { 
  val bar: Int 
  val schoko = bar + bar
}

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!
Run Code Online (Sandbox Code Playgroud)

这是一个丑陋的问题,在我个人看来,它应该在未来的Scala版本中通过修复它在编译器中消失,但是,是的,目前这也是为什么不应该使用抽象vals的原因.

编辑(2016年1月):您可以使用实现覆盖抽象val声明lazy val,这样也可以防止初始化失败.

  • 关于棘手的初始化顺序和令人惊讶的空值的话? (8认同)
  • 这可能在最近的Scala版本(此评论中为2.11.4)中已更改,但您可以使用`lazy val`覆盖`val`.如果`bar`是`val`你断言你将无法创建`F3`是不正确的.也就是说,特征中的抽象成员应该始终是"def"的 (2认同)
  • 如果你将`val bar:Int`更改为`def bar:Int``Fail.schoko`仍为零. (2认同)

ayv*_*ngo 8

我不喜欢val在traits中使用,因为val声明具有不清楚且不直观的初始化顺序.你可以在已经工作的层次结构中添加一个特征,它会破坏之前有用的所有东西,请参阅我的主题:为什么在非final类中使用普通的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
}
Run Code Online (Sandbox Code Playgroud)

此代码产生错误:

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =
Run Code Online (Sandbox Code Playgroud)

如果你花一点时间认为你会理解编译器有理由抱怨.在这种Access2.access情况下,它无法通过任何方式获得返回类型.def holder意味着它可以广泛实施.它可以为每次通话返回不同的持有者,并且持有者将包含不同Inner类型.但Java虚拟机需要返回相同的类型.

  • 初始化顺序无关紧要,但相反,我们在运行期间获得了令人惊讶的NPE,相对于反模式. (3认同)