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


Answers:


130

Adef可以通过a def,a val,alazy val或an来实现object。因此,这是定义成员的最抽象形式。由于特征通常是抽象接口,所以说您想要一个val就是说实现应如何做。如果要求a val,则实现类不能使用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
  }
}

如果你有

trait Foo { val bar: Int }

您将无法定义F1F3


好的,让您困惑并回答@ om-nom-nom -使用abstract vals可能会导致初始化问题:

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

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

在我个人看来,这是一个丑陋的问题,应该通过在编译器中对其进行修复来在以后的Scala版本中解决,但是是的,当前这也是为什么不应该使用abstract vals的原因。

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


8
关于棘手的初始化顺序和令人惊讶的null的话?
om-nom-nom

是的...我什至不去那里。的确,这些也是反对val的观点,但我认为基本动机应该只是隐藏实现。
2013年

2
这可能会在最近的斯卡拉版本(2.11.4作为此评论的)发生了变化,但是你可以重写val一个lazy val。你的说法,你将无法建立F3,如果barval不正确的。这就是说,在性状抽象成员应始终def
mplis

在富/失败例子的作品,如果你替换如预期val schoko = bar + barlazy val schoko = bar + bar。这是对初始化顺序进行某种控制的一种方法。另外,在派生类中使用lazy val代替def可以避免重新计算。
阿德里安

2
如果更改val bar: Intdef bar: Int Fail.schoko仍然是零。
Jasper-M

8

我不建议val在traits中使用,因为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情况下,它不能以任何方式导出返回类型。def holder意味着可以广泛实施。它可以为每个呼叫返回不同的持有人,并且持有人将合并不同的Inner类型。但是Java虚拟机希望返回相同的类型。


3
初始化的顺序无关紧要,但是相对于反模式,我们在运行时会得到令人惊讶的NPE。
乔纳森·诺伊费尔德

Scala具有声明式语法,在其后面隐藏了命令式性质。有时,当务之急是违反直觉的
ayvango 2014年

-4

始终使用def似乎有点尴尬,因为类似这样的方法将不起作用:

trait Entity { def id:Int}

object Table { 
  def create(e:Entity) = {e.id = 1 }  
}

您将收到以下错误:

error: value id_= is not a member of Entity

2
没关系。如果使用val而不是def,也会出现一个错误(错误:重新分配给val),这是完全合乎逻辑的。
volia17 2015年

如果使用则不行var。关键是,如果它们是字段,则应这样指定。我只是认为拥有def短视的一切。
Dimitry

@Dimitry,当然,使用var我们让您破坏封装。但是,使用def(或val)比使用全局变量更可取。我认为您正在寻找的是类似的东西,case class ConcreteEntity(override val id: Int) extends Entity因此您可以从中创建它,def create(e: Entity) = ConcreteEntity(1)这比破坏封装并允许任何类更改Entity更安全。
Jono
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.