最终的val会增加对象的大小吗?

时间:2013-07-12 13:21:34

标签: scala constants final compile-time-constant companion-object

class Foo {
  final val pi = 3
}

每个Foo对象都有pi个成员吗?因此,我应该将pi放在伴侣对象中吗?

2 个答案:

答案 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