【问题标题】:Pros and Cons of choosing def over val选择 def 而不是 val 的优点和缺点
【发布时间】:2010-11-10 06:20:31
【问题描述】:

我问的问题与this one 略有不同。假设我有一个代码 sn-p:

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

人们的想法是什么?将所有内心的vals defs 变成对我有很大的不利影响吗?

【问题讨论】:

  • 但如果只使用一次,lazy val 会比def 慢。
  • 这是真的 - 但我预计过度使用 defs 会导致 massive 类膨胀
  • 但同样,lazy vals 也是如此。所以优势仍然存在。
  • 两者都会使用方法作为访问器,所以这样想:如果你想要一个 METHOD,使用 def,如果你想要一个 VALUE MEMBER,使用 val,如果你想要一个 NON-STRICT VALUE MEMBER,使用lazy val,如果你想要一个VARIABLE MEMBER,使用var。

标签: scala lazy-evaluation


【解决方案1】:

1)

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

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

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

但是,请注意这一点 - 我实际上并没有制定广泛的基准来支持这种说法。

2)

此外,为了扩展 Kevin 的有效回复,稳定的 val 提供还意味着您可以将它与 path dependent types 一起使用 - 这是您无法使用 def 做的事情,因为编译器不会检查它的纯度。

3)

由于另一个原因,您可能想要使用 def,请参阅不久前提出的相关问题:

Functional processing of Scala streams without OutOfMemory errors

本质上,使用defs 产生Streams 确保不存在对这些对象的额外引用,这对GC 很重要。由于Streams 无论如何都是懒惰的,因此即使您有多个defs,创建它们的开销也可能可以忽略不计。

【讨论】:

    【解决方案2】:

    val 是严格的,一旦你定义了这个东西,它就会被赋予一个值。

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

    【讨论】:

    • 但是如果我只使用它一次并且它纯粹是在一个方法中,那么无论如何都不会像并发访问一样进行,对吧?我知道这两件事之间的区别是什么,我对可以编组其他参数来捍卫使用 def 感兴趣
    • 一个 val 只是一个 def(标记为 stable)加上一个支持字段。如果我知道我总是会在某个子类中提供支持(例如父类是抽象的),那么我倾向于使用 def。这样做没有特别强的逻辑,只是直觉 val 是该方法的特例具体实现。
    • 同样...对于内部 val,我知道我不会对其进行子类化,所以我只会使用 def 或惰性 val,因为它是可能不需要的昂贵计算。
    【解决方案3】:

    我可以看到一个优势,即使用def 比使用val 时您更少地绑定到某个位置。

    这不是技术优势,但在某些情况下可以实现更好的结构。

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

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

    在某些情况下,这很重要或只是方便。

    (因此,基本上,您可以使用 lazy val 实现相同的效果,但如果最多只调用一次,它可能会比 lazy val 更快。)

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • def 将被“提升”为“外部”类中的方法,而 val 将只是一个标准值
      【解决方案5】:

      在您的示例中,我将使用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 而不是 val 的另一个用例是抽象类有一个“val”,其值应该由子类提供。

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

      【讨论】:

      • 再次 - 在我的示例中,def 不能在方法中声明时被覆盖
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-15
      • 1970-01-01
      • 2011-04-11
      • 2016-10-08
      • 2010-11-01
      • 2010-09-15
      相关资源
      最近更新 更多