class Foo {
final val pi = 3
}
Does every Foo
object have a pi
member? Should I therefore put pi
in the companion object?
class Foo {
final val pi = 3
}
Does every Foo
object have a pi
member? Should I therefore put pi
in the companion object?
If you are concerned about the memory footprint, you might consider moving this field into the companion object.
Yes, every instance of class Foo
will have the pi
value -- the Scala compiler will not eliminate this declaration. The JVM reflection allows you to remove final modifiers on class members, and the Unsafe
object even allows modifying these. So -- the Scala compiler could produce code with surprising results by removing this field, so this optimization is not applied.
...
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
...
In fact, some compiler transformations (e.g. specialization) might even remove final modifiers on members under-the-hood, so something that feels final
in Scala code might not be final
on the bytecode level.
This:
class Foo[@specialized T] {
final val pi: T = null.asInstanceOf[T]
}
becomes:
...
public final T pi;
flags: ACC_PUBLIC, ACC_FINAL
Signature: #9 // TT;
public T pi();
flags: ACC_PUBLIC
LineNumberTable:
line 243: 0
...
Above, the pi
accessor method (i.e. its getter) is no longer final.
And neither will the JIT in the Oracle JVM remove this member from the object representation in memory at runtime - the runtime size of the Foo
object on a 32-bit JVM will be 16 bytes (8 bytes object header + 4 bytes for an integer field, rounded to an 8 byte boundary). The JIT might, however, decide to inline the constant value from the final field into parts of the code, so that some field writes are eliminated.
Not only will every instance have a field pi
, it will have value zero.
pi
is a constant value definition. The "accessor" just returns the constant.
This can cause problems under conditions of separate compilation and inlining, if you try hard enough.
{
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
}
Just to convince myself, upon reflection:
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
© 2022 - 2024 — McMap. All rights reserved.