class Foo {
final val pi = 3
}
每个Foo
对象都有一个pi
成员吗?因此,我应该放入pi
伴随对象吗?
class Foo {
final val pi = 3
}
每个Foo
对象都有一个pi
成员吗?因此,我应该放入pi
伴随对象吗?
如果您担心内存占用,您可以考虑将此字段移动到伴随对象中。
是的,每个类的实例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
...
事实上,一些编译器转换(例如特化)甚至可能在底层移除成员的 final 修饰符,因此final
Scala 代码中的某些东西可能不在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 也不会在运行时从内存中的对象表示中删除该成员 - Foo
32 位 JVM 上对象的运行时大小将为 16 字节(8 字节对象头 + 4 字节用于整数字段,舍入到 8 字节边界)。然而,JIT 可能会决定将最终字段中的常量值内联到部分代码中,从而消除一些字段写入。
不仅每个实例都有一个 field 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