为什么`private val`和`private final val`不同?

104

我曾经认为private valprivate final val是一样的,直到我看到Scala参考手册4.1节:

常量定义的形式为

final val x = e

e是一个常量表达式(§6.24)。最终修饰符必须存在,不能给出类型注释。对常量值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参考文档所述一致:private val并非private final val

为什么scalac不能将private val视为private final val?这背后是否有潜在的原因?


29
жҚўеҸҘиҜқиҜҙпјҢ既然valе·Із»ҸжҳҜдёҚеҸҜеҸҳзҡ„дәҶпјҢеңЁScalaдёӯдёәд»Җд№ҲиҝҳйңҖиҰҒдҪҝз”Ёfinalе…ій”®еӯ—е‘ўпјҹдёәд»Җд№Ҳзј–иҜ‘еҷЁдёҚиғҪеғҸеҜ№еҫ…final valдёҖж ·еҜ№еҫ…жүҖжңүзҡ„valпјҹ - Jesper
请注意,private 作用域修饰符在 Java 中具有与 package private 相同的语义。您可能想要使用 private[this] - Connor Doyle
6
作为包私有成员?我不这么认为:private意味着它只对该类的实例可见,而private[this]则只对该实例可见——除了同一的实例外,private不允许任何人(包括同一包内的人)访问该值。 - Make42
2个回答

84

所以,这只是一个猜测,但在Java中,final static变量的右侧带有文字值时,字节码会将其作为常量内联。这确实带来了性能上的好处,但如果“常量”发生更改,它会导致定义的二进制兼容性中断。当定义一个final static变量并且其值可能需要更改时,Java程序员必须采用hack的方式,例如使用方法或构造函数初始化该值。

Scala中的val在Java意义上已经是final了。看起来Scala的设计者正在使用冗余的修饰符final来表示“允许内联常量值”。因此,Scala程序员可以完全控制此行为,而无需采用hack:如果他们想要内联常量,即永远不应更改但速度很快的值,他们写“final val”。如果他们想要灵活地更改值而不破坏二进制兼容性,只需写“val”。


9
是的,这就是为什么非私有变量需要内联,但是私有变量显然不能在其他类中内联,否则会破坏兼容性。 - Alexey Romanov
3
当我将private val更改为private final val时,是否会存在任何二进制兼容性问题? - Yang Bo
1
@steve-waldman 对不起,您的第二段是指 val 吗? - Yang Bo
1
以下是有关Java中最终静态变量的二进制兼容性的详细信息 - http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.9 - Eran Medan

8

我认为这里的混淆在于将不可变性与final的语义混淆。子类中可以重写val,因此除非显式标记为final,否则不能将其视为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

2
我在谈论“private val”。它能被覆盖吗? - Yang Bo
不,私有值不能被覆盖。您可以在子类中重新定义另一个具有相同名称的私有值,但它是完全不同的值,只是恰好具有相同的名称。(所有对旧值的引用仍将引用旧值。) - aij
1
然而,这似乎不仅仅是覆盖行为的问题,因为我可以在解释器中创建一个最终的val(甚至是最终的var),而根本不需要处于类的上下文中。 - nairbv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接