为什么“私人 Val”和“私人最终 Val”不同?

我曾经认为 private valprivate final val是一样的,直到我在 Scala Reference 中看到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 Reference 所说: private val不是 private final val

为什么 Scalac不把 private val当作 private final val? 有什么深层次的原因吗?

21356 次浏览

So, this is just a guess, but it was a perennial annoyance in Java that final static variables with a literal on the right-hand side get inlined into bytecode as constants. That engenders a performance benefit sure, but it causes binary compatibility of the definition to break if the "constant" ever changed. When defining a final static variable whose value might need to change, Java programmers have to resort to hacks like initializing the value with a method or constructor.

A val in Scala is already final in the Java sense. It looks like Scala's designers are using the redundant modifier final to mean "permission to inline the constant value". So Scala programmers have complete control over this behavior without resorting to hacks: if they want an inlined constant, a value that should never change but is fast, they write "final val". if they want flexibility to change the value without breaking binary compatibility, just "val".

I think the confusion here arises from conflating immutability with the semantics of final. vals can be overridden in child classes and therefore can't be treated as final unless marked as such explicitly.

@Brian The REPL provides class scope at the line level. See:

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