【发布时间】:2013-07-11 01:17:57
【问题描述】:
class Foo {
final val pi = 3
}
每个Foo 对象都有pi 成员吗?因此我应该将pi 放在伴随对象中吗?
【问题讨论】:
标签: scala constants final compile-time-constant companion-object
class Foo {
final val pi = 3
}
每个Foo 对象都有pi 成员吗?因此我应该将pi 放在伴随对象中吗?
【问题讨论】:
标签: scala constants final compile-time-constant companion-object
不仅每个实例都有一个字段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
【讨论】:
如果您担心内存占用,您可以考虑将此字段移到伴随对象中。
是的,Foo 类的每个实例都将具有pi 值——Scala 编译器不会消除此声明。 JVM 反射允许您删除类成员上的 final 修饰符,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 修饰符,因此在 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 可能会决定将最终字段中的常量值内联到部分代码中,从而消除一些字段写入。
【讨论】: