变量a 本身仍然在outer 方法的堆栈帧中,而它所引用的对象是在堆上分配的,就像所有Java 对象一样(即使a 应该表示一个原语类型)。
通过在您的代码上运行javap -v,我们可以看到a 实际上是scala.runtime.IntRef 类型的最终变量,它包含一个可以更新的整数字段。嵌套方法inner 变成了一个静态方法,它接受一个IntRef 类型的参数并将其elem 字段设置为42。这有点类似于C# 方法,但为每个变量创建一个对象而不是一个结构来保存所有这些。
public int outer();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: invokestatic #16 // Method scala/runtime/IntRef.create:
(I)Lscala/runtime/IntRef;
4: astore_1
5: aload_1
6: invokestatic #20 // Method inner$1:(Lscala/runtime/IntRef;)V
9: aload_1
10: getfield #24 // Field scala/runtime/IntRef.elem:I
13: ireturn
编辑:这次让我们用String 试试吧:
class ClosureTest {
def outer = {
var a = ""
def inner() = {
a = "42"
}
inner()
a
}
}
来自javap的输出:
public java.lang.String outer();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: ldc #12 // String
2: invokestatic #18 // Method scala/runtime/ObjectRef.create:(Ljava/lang/Object;)Lscala/runtime/ObjectRef;
5: astore_1
6: aload_1
7: invokestatic #22 // Method inner$1: (Lscala/runtime/ObjectRef;)V
10: aload_1
11: getfield #26 // Field
scala/runtime/ObjectRef.elem:Ljava/lang/Object;
14: checkcast #28 // class java/lang/String
17: areturn
这一次,由于String 不是原始类型,所以使用了ObjectRef 类(它有一个表示包装值的类型参数),但它基本上还是一样的。尽管 JVM 不允许像 C# 那样拥有 ref 参数,但对象仍然通过引用传递,因此 a 持有的对象/原语的值仍然可以修改。
这是我能找到的唯一文档的link。还有很多其他类,比如BooleanRef、FloatRef,以及它们的易失类,比如VolatileDoubleRef、VolatileObjectRef等。这些类中的每一个基本上只有一个可变的公共字段,编译器在需要捕获变量的“真实”值。