class Foo {
final val pi = 3
}
每个Foo
对象都有pi
个成员吗?因此,我应该将pi
放在伴侣对象中吗?
答案 0 :(得分:4)
如果您担心内存占用,可以考虑将此字段移动到配套对象中。
是的,类Foo
的每个实例都有pi
值 - Scala编译器不会删除此声明。 JVM反射允许您删除类成员上的最终修饰符,Unsafe
对象甚至允许修改它们。因此 - Scala编译器可以通过删除此字段来生成具有令人惊讶的结果的代码,因此不会应用此优化。
...
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_SUPER
...
{
private final int pi;
flags: ACC_PRIVATE, ACC_FINAL
public final int pi();
flags: ACC_PUBLIC, ACC_FINAL
LineNumberTable:
line 243: 0
LocalVariableTable:
Start Length Slot Name Signature
...
实际上,某些编译器转换(例如,特化)甚至可能会删除成员内部的最终修饰符,因此在字节码级别上,Scala代码中的final
感觉可能不是final
此:
class Foo[@specialized T] {
final val pi: T = null.asInstanceOf[T]
}
变为:
...
public final T pi;
flags: ACC_PUBLIC, ACC_FINAL
Signature: #9 // TT;
public T pi();
flags: ACC_PUBLIC
LineNumberTable:
line 243: 0
...
上面,pi
访问器方法(即其getter)不再是最终的。
Oracle JVM中的JIT也不会在运行时从内存中的对象表示中删除此成员 - 32位JVM上的Foo
对象的运行时大小将为16字节(8字节对象头) + 4个字节表示整数字段,四舍五入为8字节边界)。但是,JIT可能决定将最终字段中的常量值内联到代码的某些部分,以便消除一些字段写入。
答案 1 :(得分:3)
每个实例不仅有一个字段pi
,它的值为零。
pi
是一个常量值定义。 “访问者”只返回常量。
如果您努力尝试,这可能会在单独编译和内联的情况下导致问题。
{
private final int pi;
flags: ACC_PRIVATE, ACC_FINAL
public final int pi();
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=1, args_size=1
0: iconst_3
1: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this LFoo;
LineNumberTable:
line 8: 0
public Foo();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #14 // Method java/lang/Object."<init>":()V
4: return
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LFoo;
LineNumberTable:
line 13: 0
}
只是为了说服自己,经过反思:
scala> res5.tail
res16: Iterable[reflect.runtime.universe.Symbol] = List(value pi)
scala> res5.last.asTerm.isAccessor
res18: Boolean = false
scala> res5.head.asTerm.isAccessor
res19: Boolean = true
scala> res0 reflectField res5.last.asTerm
res21: reflect.runtime.universe.FieldMirror = field mirror for Foo.pi (bound to Foo@2907f26d)
scala> res21.get
res22: Any = 0