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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19642053/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:48:04  来源:igfitidea点击:

When to use val or def in Scala traits?

scalainheritancetraits

提问by Mansur Ashraf

I was going through the effective scala slidesand it mentions on slide 10 to never use valin a traitfor abstract members and use definstead. The slide does not mention in detail why using abstract valin a traitis an anti-pattern. I would appreciate it if someone can explain best practice around using val vs def in a trait for abstract methods

我正在浏览有效的 scala 幻灯片,它在幻灯片 10 中提到永远不要valtrait抽象成员中使用,def而是使用。幻灯片没有详细提到为什么val在 a 中使用 abstracttrait是一种反模式。如果有人可以解释在抽象方法的特征中使用 val 与 def 的最佳实践,我将不胜感激

回答by 0__

A defcan be implemented by either of a def, a val, a lazy valor an object. So it's the most abstract form of defining a member. Since traits are usually abstract interfaces, saying you want a valis saying howthe implementation should do. If you ask for a val, an implementing class cannot use a def.

Adef可以通过 a def、 a val、 alazy val或 an 中的任何一个来实现object。所以它是定义成员的最抽象的形式。由于性状通常是抽象接口,说你想有一个val是说如何实施应该做的。如果您要求 a val,则实现类不能使用 a def

A valis needed only if you need a stable identifier, e.g. for a path-dependent type. That's something you usually don't need.

val仅当您需要一个稳定的标识符时才需要A ,例如对于依赖于路径的类型。这是你通常不需要的东西。



Compare:

比较:

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
  }
}

If you had

如果你有

trait Foo { val bar: Int }

you wouldn't be able to define F1or F3.

你将无法定义F1F3



Ok, and to confuse you and answer @om-nom-nom—using abstract vals can cause initialisation problems:

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

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

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

This is an ugly problem which in my personal opinion should go away in future Scala versions by fixing it in the compiler, but yes, currently this is also a reason why one should not use abstract vals.

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

Edit(Jan 2016): You are allowed to override an abstract valdeclaration with a lazy valimplementation, so that would also prevent the initialisation failure.

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

回答by ayvango

I prefer not use valin traits because the val declaration has unclear and non-intuitive order of initialization. You may add a trait to already working hierarchy and it would break all things that worked before, see my topic: why using plain val in non-final classes

我不喜欢val在特征中使用,因为 val 声明的初始化顺序不清楚且不直观。您可以向已经工作的层次结构添加一个特征,它会破坏以前工作的所有内容,请参阅我的主题:为什么在非最终类中使用普通 val

You should keep all things about using this val declarations in mind which eventually road you to an error.

您应该牢记有关使用此 val 声明的所有事项,这最终会导致您出错。



Update with more complicated example

更新更复杂的例子

But there are times when you could not avoid using val. As @0__ had mentioned sometimes you need a stable identifier and defis not one.

但有时您无法避免使用val. 正如@0__ 所提到的,有时您需要一个稳定的标识符而def不是一个。

I would provide an example to show what he was talking about:

我会举一个例子来说明他在说什么:

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
}

This code produces the error:

此代码产生错误:

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =

If you take a minute to think you would understand that compiler has a reason to complain. In the Access2.accesscase it could not derive return type by any means. def holdermeans that it could be implemented in broad way. It could return different holders for each call and that holders would incorporate different Innertypes. But Java virtual machine expects the same type to be returned.

如果您花一点时间认为您会理解编译器有理由抱怨。在这种Access2.access情况下,它无法以任何方式派生返回类型。def holder意味着它可以广泛实施。它可以为每次调用返回不同的持有人,并且持有人将合并不同的Inner类型。但是 Java 虚拟机期望返回相同的类型。

回答by Dimitry

Always using def seems a bit awkward since something like this won't work:

总是使用 def 似乎有点尴尬,因为这样的事情是行不通的:

trait Entity { def id:Int}

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

You will get the following error:

您将收到以下错误:

error: value id_= is not a member of Entity