Reputation: 60
I have a code like this:
public class App {
private final String some;
public App(){
some = "old";
}
public static void main(String... args) throws NoSuchFieldException, IllegalAccessException {
App a = new App();
a.magic();
System.out.println(a.some);
}
private void magic() throws NoSuchFieldException, IllegalAccessException {
Field field = this.getClass().getDeclaredField("some");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(this, "new");
String someDuplicate = (String) field.get(this);
System.out.println(someDuplicate);
}
}
the output from this will be
new
new
but if i'll change variable initialization to this:
private final String some = "old";
the output will be
new
old
seems like inline initialization causes static-like behavior of final non-static field
I could'n find any dock reference to this behavior, may be there is some logical explanation to this.
By the way this way to init field causes behavior like in constructor init case:
{
some = "old";
}
Upvotes: 1
Views: 60
Reputation: 533560
The javac
does constant inlining. When you have a code such as
class A {
final String text = "Hello";
public static void main(String... args) {
System.out.println(new A().text);
}
}
The javac
can inline the constant as it is known at compile time. This makes changing the underlying field have no effect on places it has been inlined.
By moving the value to the constructor, it is no longer known at compile time.
Dumping the byte code for the main
method you can see it doesn't read the field but rather LDC
loads the constant "Hello"
public static varargs main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW A
DUP
INVOKESPECIAL A.<init> ()V
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
LDC "Hello"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
What I find interesting is that it still creates the A
and checks it for null using .getClass()
so it's optimisation only go so far.
BTW You can work around this without using a constructor/initialisation block with a wrapping method.
class A {
final String text = dynamic("Hello");
// or final String text = String.valueOf("Hello");
public static void main(String... args) {
System.out.println(new A().text);
}
static <T> T dynamic(T t) {
return t;
}
}
or any expression it can't determine at compile time.
Upvotes: 5