为啥`private val`和`private final val`不同?

Posted

技术标签:

【中文标题】为啥`private val`和`private final val`不同?【英文标题】:Why are `private val` and `private final val` different?为什么`private val`和`private final val`不同? 【发布时间】:2012-11-04 22:19:36 【问题描述】:

我以前认为private valprivate final val 是一样的,直到我在Scala Reference 中看到了第4.1 节:

常量值定义的格式为

final val x = e

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

我已经写了一个测试:

class PrivateVal 
  private val privateVal = 0
  def testPrivateVal = privateVal
  private final val privateFinalVal = 1
  def testPrivateFinalVal = privateFinalVal

javap -c 输出:

Compiled from "PrivateVal.scala"
public class PrivateVal 
  public int testPrivateVal();
    Code:
       0: aload_0       
       1: invokespecial #19                 // Method privateVal:()I
       4: ireturn       

  public int testPrivateFinalVal();
    Code:
       0: iconst_1      
       1: ireturn       

  public PrivateVal();
    Code:
       0: aload_0       
       1: invokespecial #24                 // Method java/lang/Object."<init>":()V
       4: aload_0       
       5: iconst_0      
       6: putfield      #14                 // Field privateVal:I
       9: return

字节码正如Scala Reference所说:private val is not private final val

scalac 为什么不将private val 视为private final val?有什么根本原因吗?

【问题讨论】:

换句话说:既然val 已经是不可变的,为什么我们在Scala 中还需要final 关键字呢?为什么编译器不能像对待final vals 一样对待所有vals? 请注意,private 作用域修饰符与 Java 中的 package private 具有相同的语义。你可能想说private[this] @ConnorDoyle:作为包私有?我不这么认为:private 的意思是它只对这个类的实例可见,private[this] 只有这个实例——除了同一个的实例,private 不允许任何人(包括来自同一个包)来访问该值。 【参考方案1】:

所以,这只是一个猜测,但在 Java 中,具有右侧文字的最终静态变量作为常量内联到字节码中是一个长期的烦恼。这肯定会产生性能优势,但如果“常量”发生变化,它会导致定义的二进制兼容性中断。在定义最终的静态变量时,其值可能需要更改,Java 程序员不得不求助于使用方法或构造函数初始化值等技巧。

Scala 中的 val 在 Java 意义上已经是最终的。看起来 Scala 的设计者正在使用冗余修饰符 final 来表示“允许内联常量值”。因此 Scala 程序员可以完全控制这种行为而无需求助于黑客:如果他们想要一个内联常量,一个永远不会改变但速度很快的值,他们会写“final val”。如果他们希望在不破坏二进制兼容性的情况下灵活地更改值,只需 "val"。

【讨论】:

是的,这就是非私有 val 的原因,但私有 val 显然不能内联在其他类中,并以同样的方式破坏兼容性。 private val 更改为private final val 时是否存在二进制兼容性问题? @steve-waldman 打扰一下,您的第二段是指val 吗? 这里是 Java 中关于二进制兼容性的最终静态变量的详细信息 - docs.oracle.com/javase/specs/jls/se7/html/…【参考方案2】:

我认为这里的混淆源于将不变性与 final 的语义混为一谈。 vals 可以在子类中被覆盖,因此不能被视为 final,除非明确标记。

@Brian REPL 在行级别提供类范围。见:

scala> $iw.getClass.getPackage
res0: Package = package $line3

scala> private val x = 5
<console>:5: error: value x cannot be accessed in object $iw
  lazy val $result = `x`

scala> private val x = 5; println(x);
5

【讨论】:

我说的是private val。可以覆盖吗? 不,不能覆盖私有 val。您可以在子类中重新定义另一个具有相同名称的私有 val,但它是一个完全不同的 val,只是恰好具有相同的名称。 (所有对旧版本的引用仍然是旧版本。) 它似乎不仅仅是这种压倒一切的行为,因为我可以在解释器中创建一个最终的 val(甚至是一个最终的 var),而无需在一个类的上下文中。

以上是关于为啥`private val`和`private final val`不同?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 HttpCacheability.Private 会抑制 ETag?

为啥 Visual Studio 告诉我需要引用 System.Private.CoreLib?

为啥 JUnit 5 默认访问修饰符更改为 package-private

为啥内部类的private变量可被外部类直接访问

在非常量对象上,为啥 C++ 不调用具有 public-const 和 private-nonconst 重载的方法的 const 版本?

为啥 private(set) 在 Swift 中不起作用?