问题 选择def val的优点和缺点


我问的是一个略有不同的问题 这个。假设我有一个代码片段:

def foo(i : Int) : List[String] = {
  val s = i.toString + "!" //using val
  s :: Nil
}

这在功能上等同于以下内容:

def foo(i : Int) : List[String] = {
  def s = i.toString + "!" //using def
  s :: Nil
}

为什么我会选择一个而不是另一个?显然我会认为第二个有一点点缺点:

  • 创建更多字节码(内部 def 被提升到班上的方法)
  • 调用方法而不是访问值的运行时性能开销
  • 非严格的评估意味着我可以轻松访问 s 两次(即不必要的重做计算)

唯一的 优点 我能想到的是:

  • 非严格评价 s 意味着它只在被使用时被调用(但是我可以使用它 lazy val

这里的人们的想法是什么?是否有一个显着的不利于我的内心 val小号 defS'


7869
2017-11-09 12:09


起源

但如果只使用一次 lazy val 会慢一点 def。 - Debilski
这是事实 - 但我希望过度使用 defs会导致 大规模的 阶级膨胀 - oxbow_lakes
但同样,这也是如此 lazy val秒。所以优势仍然存在。 - Debilski
两者都会使用一个方法作为访问器,所以想一想:如果你想要一个METHOD,使用def,如果你想要一个VALUE MEMBER,使用val,如果你想要一个非STRICT VALUE MEMBER,使用lazy val,如果你想要一个VARIABLE MEMBER,使用var。 - Anonymous


答案:


1)

我没有看到的一个答案是你所描述的方法的堆栈框架实际上可能更小。每 val 你声明将占用JVM堆栈上的一个插槽,但是,只要你使用一个 def 获得的值将在你使用它的第一个表达式中消耗掉。即使是 def 引用来自环境的东西,编译器将通过。 HotSpot应该优化这些东西,或者有些人声称。看到:

http://www.ibm.com/developerworks/library/j-jtp12214/

由于内部方法被编译为场景后面的常规私有方法并且通常非常小,因此JIT编译器可能会选择内联它然后对其进行优化。这可以节省分配较小堆栈帧的时间(?),或者通过在堆栈上使用较少的元素,使局部变量访问更快。

但是,拿这个(大)盐 - 我实际上没有做出广泛的基准来备份这个说法。

2)

另外,要扩大凯文的有效回复,稳定 val 提供也意味着您可以使用它 路径依赖类型  - 你不能做的事情 def,因为编译器不检查其纯度。

3)

出于另一个原因,您可能想要使用 def,看到不久前问过的一个相关问题:

Scala流的功能处理没有OutOfMemory错误

基本上,使用 def生产 Streams 确保不存在对这些对象的附加引用,这对GC很重要。以来 Stream无论如何,s是懒惰的,即使你有多个,创建它们的开销也许可以忽略不计 def秒。


6
2017-11-09 17:16





val是严格的,只要你定义了它就给它一个值。

在内部,编译器会将其标记为STABLE,相当于Java中的final。这个 应该 允许JVM进行各种优化 - 我只是不知道它们是什么:)


3
2017-11-09 14:28



但是,如果我只使用它一次并且它纯粹是在一个方法中,那就不像有任何像并发访问那样的事情,对吧?我知道这两件事之间的区别是什么,我对其他参数可以在防守使用方面的编制感兴趣 def - oxbow_lakes
val只是一个def(标记为稳定)加上一个支持字段。如果我知道我总是会在某个子类中提供支持(例如,父级是抽象的),那么我倾向于选择def。这样做没有特别强大的逻辑,只是一种直觉,即val是该方法的特例具体实现。 - Kevin Wright
同样......有一个内部值我知道我不会将它子类化,所以我只使用def或lazy val,这是一个可能不需要的昂贵计算。 - Kevin Wright


我可以看到一个优点,即你在使用时不那么受某个位置的束缚 def比使用时 val

这不是技术优势,但在某些情况下允许更好的结构。

所以,愚蠢的例子(请编辑这个答案,如果你有一个更好的答案),这是不可能的 val

def foo(i : Int) : List[String] = {
  def ret = s :: Nil
  def s = i.toString + "!"
  ret
}

可能存在重要或方便的情况。

(所以,基本上,你可以实现同样的目标 lazy val 但是,如果最多只调用一次,它可能会比a更快 lazy val。)


2
2017-11-09 17:02





对于像这样的局部声明(没有参数,精确计算一次并且在声明点和评估点之间没有评估代码),没有语义差异。如果“val”版本编译为比“def”版本更简单,更高效的代码,我不会感到惊讶,但你必须检查字节码和可能的配置文件。


0
2017-11-09 12:57



该 def 将被“提升”到“外部”类中的方法,而val将只是一个标准值 - oxbow_lakes


在你的例子中,我会使用一个 val。我认为在声明类成员时val / def的选择更有意义:

class A { def a0 = "a"; def a1 = "a" }

class B extends A {
  var c = 0
  override def a0 = { c += 1; "a" + c }
  override val a1 = "b" 
}

在基类中使用 def 允许子类覆盖可能不返回常量的def。或者它可以用val覆盖。因此,它比val更具灵活性。

编辑:使用def over val的另一个用例是当抽象类具有“val”时,该值应由子类提供。

abstract class C { def f: SomeObject }
new C { val f = new SomeObject(...) }

0
2017-11-09 14:25



再次 - 在我的例子中, def  不能 被覆盖,因为它是在方法中声明的 - oxbow_lakes