为什么“ private val”和“ private final val”不同?


100

我常想,private valprivate final val一样,直到我在斯卡拉参考看到第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不是private final val

为什么不scalac只是把private val作为private final val?有什么根本原因吗?


28
换句话说:由于a val已经是不可变的,为什么我们final在Scala中根本不需要关键字呢?为什么编译器不能将vals与s一样对待final val
Jesper 2012年

注意,private作用域修饰符的语义与package privateJava中的相同。您可能要说private[this]
康纳·道尔

5
@ConnorDoyle:作为包私有吗?我不这么认为:private意味着它仅仅是这一类,实例可见private[this]只有这种情况下-除了相同的实例private不允许任何人(来自同一个包包括)来访问值。
Make42 '16

Answers:


81

因此,这只是一个猜测,但这是Java中的一个常年烦恼,它将右侧带有文字的最终静态变量作为常量内联到字节码中。这确保了性能上的好处,但是如果“常数”发生了变化,它将导致定义的二进制兼容性被破坏。在定义可能需要更改其值的最终静态变量时,Java程序员必须诉诸于黑客,例如使用方法或构造函数初始化该值。

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


9
是的,这就是非私有val的原因,但是私有val显然不能内联到其他类中,并且不能以相同的方式破坏兼容性。
阿列克谢·罗曼诺夫

3
是否有任何二进制兼容性问题,当我换private valprivate final val
杨波2012年

1
@ steve-waldman对不起,您是说val第二段吗?
Yang Bo

1
以下是有关二进制兼容性的Java最终静态变量的详细信息-docs.oracle.com/javase/specs/jls/se7/html/…–
Eran Medan

8

我认为这里的混淆来自将不变性与final的语义相混淆。 val可以在子类中覆盖,因此除非明确标记为,否则不能将其视为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

1
我在说private val。可以覆盖吗?
杨波2012年

不,私有值不能被覆盖。您可以在子类中重新定义另一个具有相同名称的私有val,但这是一个完全不同的val,恰好具有相同的名称。(所有提到旧版本的内容仍然会引用旧版本。)
aij 2014年

1
但是,这似乎不只是这种压倒性的行为,因为我可以在解释器中创建最终的val(甚至最终的var),而完全不必在类的上下文中。
nairbv 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.