【问题标题】:Why is a `val` inside an `object` not automatically final?为什么 `object` 中的 `val` 不会自动成为 final?
【发布时间】:2012-08-31 19:21:29
【问题描述】:

vals 不是(?)在单例对象中自动成为 final 的原因是什么?例如

object NonFinal {
   val a = 0
   val b = 1

   def test(i: Int) = (i: @annotation.switch) match {
      case `a` => true
      case `b` => false
   }
}

结果:

<console>:12: error: could not emit switch for @switch annotated match
          def test(i: Int) = (i: @annotation.switch) match {
                                                     ^

object Final {
   final val a = 0
   final val b = 1

   def test(i: Int) = (i: @annotation.switch) match {
      case `a` => true
      case `b` => false
   }
}

编译时没有警告,因此可能会生成更快的模式匹配表。

必须添加final 对我来说似乎是纯粹的烦人噪音。 object 本身不是最终的,因此也是它的成员?

【问题讨论】:

  • 嗯。可能使用的特征呢?
  • @TonyK。 - 你的意思是?即使我有trait T { def a: Int },该对象也会在线性化规则中覆盖a。如果我有trait T { def a: Int = 33 },那么在这种情况下override final val 是不可能的。但我认为这仍然不会取消使 non-overriding 默认为 final 的方法。
  • 同意。我对 Scala 比较陌生...正在尝试探索您没有想到的可能途径。
  • 您应该将接受的答案更改为@extempore (Paul Phillips)。在这种情况下,final 导致在编译期间内联值。

标签: scala field final


【解决方案1】:

这在the specification 中明确解决,它们自动成为最终的:

final 类或对象的成员隐式也是 final,所以 final 修饰符通常对他们来说也是多余的。但是请注意, 常量值定义(第 4.1 节)确实需要显式的 final 修饰符,即使 它们在最终类或对象中定义。

您的 final-less 示例使用 2.10-M7 编译时没有错误(或警告),因此我假设 @switch 在早期版本中检查存在问题,并且成员实际上是最终版本。


更新:实际上这比我预期的更奇怪——如果我们使用 2.9.2 或 2.10-M7 编译以下代码:

object NonFinal {
  val a = 0
}

object Final {
  final val a = 0
}

javap 确实有所不同:

public final class NonFinal$ implements scala.ScalaObject {
  public static final NonFinal$ MODULE$;
  public static {};
  public int a();
}

public final class Final$ implements scala.ScalaObject {
  public static final Final$ MODULE$;
  public static {};
  public final int a();
}

即使值定义的右侧不是常量表达式,您也会看到相同的内容。

所以我会留下我的答案,但这不是决定性的。

【讨论】:

  • 我以为您只能打开常量值,在这种情况下,2.10-M7 已删除您引用的final 的要求。没有?
  • @RexKerr:确实。我不记得在任何更改日志中看到过,但我会再次检查。
  • 谢谢,那个规范引用很清楚,说vals 确实需要明确的final。 2.10.0-M7 很奇怪,可能值得检查生成的模式匹配代码(也许 @switch 被默默删除了?)
  • @0__:您认为它需要修饰符?我现在对此感到困惑,但至少会将其解释为如果没有机会定义常量值(例如,我们在右侧有"0".toIntnew A side) 那么final 是多余的。
  • 是的,我是这样读的,虽然我不同意这里的规范。正如我所说,我认为这是不必要的噪音。
【解决方案2】:

为了解决关于对象 final 的核心问题,我认为规范中的这个子句更相关:

常量值定义的形式为 final val x = e 其中 e 是一个常量表达式(第 6.24 节)。 final 修饰符必须存在,并且不能给出类型注释。对常量值 x 的引用本身被视为常量表达式;在生成的代码中,它们被定义的右侧 e 替换。

意义重大:

  • 不能给出类型注释
  • 在生成的代码中使用了表达式 e(根据我的阅读,作为原始未计算的常量表达式)

在我看来,规范要求编译器使用这些更像是宏替换,而不是在编译时就地评估的值,这可能会影响生成的代码的运行方式。

我觉得特别有意思的是不能给出类型注解。

我认为这指向了我们的最终答案,尽管我无法举出一个示例来显示这些要求的运行时差异。事实上,在我的 2.9.2 解释器中,我什至没有执行第一条规则。

【讨论】:

    【解决方案3】:

    您不是在问“为什么它们不是最终的”,而是在问“为什么它们不是内联的”。碰巧 final 是您如何提示您希望它们内联的编译器。

    它们没有自动内联的原因是单独编译。

    object A { final val x = 55 }
    object B { def f = A.x }
    

    当你编译这个时,B.f 返回 55,字面意思是:

    public int f();
      0: bipush        55
      2: ireturn       
    

    这意味着如果您重新编译 A,B 将忘记所做的更改。如果 x 在 A 中未标记为 final,则 B.f 看起来像这样:

      0: getstatic     #19                 // Field A$.MODULE$:LA$;
      3: invokevirtual #22                 // Method A$.x:()I
      6: ireturn       
    

    另外,为了纠正其他答案之一,final 并不意味着在 scala 中不可变。

    【讨论】:

    • final 也不意味着恒定。试试“final var x = 1 ; x = 2”。
    猜你喜欢
    • 2012-11-04
    • 2015-09-25
    • 1970-01-01
    • 2011-10-20
    • 2018-06-27
    • 1970-01-01
    • 2011-05-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多