Volker Simonis [Фолкер Симонис], SAP / volker.simonis@gmail.com
“A
final
variable may only be assigned to once..”“Once a final variable has been assigned, it always contains the same value.”
class Test0 {
private final int f;
public Test0() {
}
}
Compile time error: variable f
might not have been initialized.
class TestA {
private final int f = 0;
public TestA(int f) {
this.f = f;
}
}
Compile time error: cannot assign a value to final variable f
class TestB {
private final int f;
{
f = 0; // Instance initializer
}
public TestB(int f) {
this.f = f;
}
}
Compile time error: cannot assign a value to final variable f
class TestC {
private final int f;
public TestC(int f) {
this.f = f;
}
}
OK: since Java 1.1 (“blank final”).
class TestF {
private final int f;
public TestF(int f) {
if (f > 0) {
this.f = f;
} else {
this.f = 42;
}
}
}
OK: javac
does a certain amount of flow analysis..
0) {
this.f = f;
}
if (f <= 0) {
this.f = 42;
}
}
}
]]>
..but not too much!
Compile time error: variable f
might already have been initialized.
class TestG1 {
private final int f;
public TestG1(int f) {
try {
this.f = f;
}
catch (Exception e) {
}
}
}
Compile time error: variable f
might not have been initialized.
class TestG1 {
private final int f;
public TestG1(int f) {
try {
this.f = f;
}
catch (Exception e) {
this.f = 42;
}
}
}
Compile time error: variable f
might already have been initialized.
class TestG1 {
public int f;
public TestG1(int f) {
try {
this.f = f;
}
catch (Exception e) {
this.f = 42;
}
}
}
Without final
OK
4: aload_0
5: iload_1
6: putfield #2 // Field f:I
9: goto 19
12: astore_2
13: aload_0
14: bipush 42
16: putfield #2 // Field f:I
19: return
Exception table:
from to target type
4 9 12 Exception
]]>
class TestG1 {
public final int f;
public TestG1(int f) {
try {
this.f = f;
}
catch (Exception e) {
this.f = 42;
}
}
}
With final
compile Error!
But we can generate the Bytecode:
4: aload_0
5: iload_1
6: putfield #2 // Field f:I
9: goto 19
12: astore_2
13: aload_0
14: bipush 42
16: putfield #2 // Field f:I
19: return
Exception table:
from to target type
4 9 12 Exception
]]>
“If the field is final the
putfield
instruction must occur in
an instance initialization method (<init>) of the current class.”
No or multiple assignmets within constructors are possible according to the JVMS.
jdk.internal.org.objectweb.asm
", "(I)V", null, null);
Label l_try_beg = new Label(), l_try_end = new Label();
Label l_catch = new Label(), l_end = new Label();
constr.visitCode();
constr.visitVarInsn(ALOAD, 0);
constr.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
constr.visitLabel(l_try_beg);
constr.visitVarInsn(ALOAD, 0);
constr.visitVarInsn(ILOAD, 1);
constr.visitFieldInsn(PUTFIELD, "TestG1", "f", "I");
constr.visitLabel(l_try_end);
constr.visitJumpInsn(GOTO, l_end);
constr.visitLabel(l_catch);
constr.visitVarInsn(ASTORE, 2);
constr.visitVarInsn(ALOAD, 0);
constr.visitIntInsn(BIPUSH, 42);
constr.visitFieldInsn(PUTFIELD, "TestG1", "f", "I");
constr.visitLabel(l_end);
constr.visitInsn(RETURN);
constr.visitTryCatchBlock(l_try_beg, l_try_end, l_catch, "java/lang/Exception");
// max stack and max locals are automatically computed (because of the
// 'ClassWriter.COMPUTE_FRAMES' option) in the ClassWriter constructor,
// but we need this call nevertheless in order for the computation to happen!
constr.visitMaxs(0, 0);
constr.visitEnd();
// Get the bytes of the class..
byte[] b = cw.toByteArray();
// ..and write them into a class file (for debugging)
FileOutputStream fos = new FileOutputStream("TestG1.class");
fos.write(b);
fos.close();
// Load the newly created class..
Final f = new Final();
Class> testG1Class = f.defineClass("TestG1", b, 0, b.length);
// ..get the constructor..
Constructor c = testG1Class.getConstructor(int.class);
// ..and create a new "TestG1" object
Object testG1 = c.newInstance(42);
Field int_f = testG1Class.getDeclaredField("f");
System.out.println("testG1.f = " + int_f.getInt(testG1));
}
]]>
Compile with “javac javac -XDignore.symbol.file=true
”
class TestH {
private final int f;
{
foo();
}
public TestH(int f) {
this.f = f;
System.out.println(this + ".f = " + this.f);
}
public void foo() {
System.out.println(this + ".f = " + this.f);
}
}
new TestH(42);
TestH@15db9742.f = 0 TestH@15db9742.f = 42
SEI CERT: TSM01-J. Do not let the "this" reference escape during object construction!
class TestH2 {
private static final int f;
static {
f = foo();
}
public TestH2() {
System.out.println(TestH2.class + ".f = " + f);
}
static int foo() {
System.out.println(TestH2.class + ".f = " + f);
return 42;
}
}
new TestH2();
class Final$TestH2.f = 0 class Final$TestH2.f = 42
JDK9 Bug: 8087161: Fails to start up initialize system class loader on unsupported charset
class TestI {
public final int f;
public TestI(int f) {
this.f = f;
}
public void set(int f) {
// change value of 'f'
}
}
TestI i = new TestI(42);
i.set(99);
System.out.println("i.f = " + i.f);
i.f = 99
Question: How can we change the value of 'f' in Java?
“The JNI does not enforce class, field, and method access control restrictions that can be expressed at the Java programming language level through the use of modifiers such as private and final.”
class TestI {
...
public native void set(int f);
static {
System.loadLibrary("TestI1");
}
}
GetObjectClass(obj);
jfieldID fid = env->GetFieldID(thisClass, "f", "I");
env->SetIntField(obj, fid, val);
}
]]>
JDK9 RFE: 8058164: final fields in objects need to support inlining optimizations
sun.misc.Unsafe
unsafeConstructor =
sun.misc.Unsafe.class.getDeclaredConstructor();
unsafeConstructor.setAccessible(true);
UNSAFE = unsafeConstructor.newInstance();
} catch (Exception e) {}
}
public void set(int f) throws Exception {
java.lang.reflect.Field field = this.getClass().getDeclaredField("f");
UNSAFE.putInt(this, UNSAFE.objectFieldOffset(field), f);
}
}
]]>
Unchanged 42 Unchanged 99
Unchanged 42 Changed 99
“We call a variable, of primitive type or type String, that is
final
and
initialized with a compile-time constant expression a constant variable.”
“References to fields that are constant variables are resolved at compile time to the constant value that is denoted. No reference to such a constant field should be present in the code...”
Constant variables are inlined by javac
!
J. Bloch, N. Gafter - “Java Puzzlers”, Nr. 93
42
Test.set_test(new Test(99));
System.out.println(Test.get_f());
Test test42 = new Test(42);
Test.set_test(test42);
for (int n = 0; n < 20000; n++)
if (Test.get_f() != 42)
System.out.println("!!!");
Test.set_test(new Test(99));
System.out.println(Test.get_f());
Test.test.set_f(99);
System.out.println(Test.get_f());
test42.set_f(99);
System.out.println(Test.get_f());
]]>
42
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 99
Test test42 = new Test(42);
Test.set_test(test42);
for (int n = 0; n < 20000; n++)
if (Test.get_f() != 42)
System.out.println("!!!");
Test.set_test(new Test(99));
System.out.println(Test.get_f());
Test.test.set_f(99);
System.out.println(Test.get_f());
test42.set_f(99);
System.out.println(Test.get_f());
]]>
42
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 99
Test test42 = new Test(42);
Test.set_test(test42);
for (int n = 0; n < 20000; n++)
if (Test.get_f() != 42) // -> 42
System.out.println("!!!");
Test.set_test(new Test(99));
System.out.println(Test.get_f());
Test.test.set_f(99);
System.out.println(Test.get_f());
test42.set_f(99);
System.out.println(Test.get_f());
]]>
42
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 99
Test test42 = new Test(42);
Test.set_test(test42);
for (int n = 0; n < 20000; n++)
if (Test.get_f() != 42) // -> 42
System.out.println("!!!");
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 42
Test.test.set_f(99);
System.out.println(Test.get_f());
test42.set_f(99);
System.out.println(Test.get_f());
]]>
42
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 99
Test test42 = new Test(42);
Test.set_test(test42);
for (int n = 0; n < 20000; n++)
if (Test.get_f() != 42) // -> 42
System.out.println("!!!");
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 42
Test.test.set_f(99);
System.out.println(Test.get_f()); // -> 42
test42.set_f(99);
System.out.println(Test.get_f());
]]>
42
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 99
Test test42 = new Test(42);
Test.set_test(test42);
for (int n = 0; n < 20000; n++)
if (Test.get_f() != 42) // -> 42
System.out.println("!!!");
Test.set_test(new Test(99));
System.out.println(Test.get_f()); // -> 42
Test.test.set_f(99);
System.out.println(Test.get_f()); // -> 42
test42.set_f(99);
System.out.println(Test.get_f()); // ???
]]>
-XX:-TrustFinalNonStaticFields
-> 99
-XX:+TrustFinalNonStaticFields
-> 42
JDK 7:
6912065: final fields in objects need to support inlining optimizations for JSR 292
By default only enabled for java.lang.invoke
and sun.invoke
.
@Stable
Annotationjava.lang.invoke
package.-XX:+FoldStableValues
-XX:+UseImplicitStableValues