Your Test
compiles into:
public class Test {
public static final Test me;
public static final Integer I;
public static final String S = "abc";
static {
me = new Test();
I = Integer.valueOf(4);
}
public Test() {
System.out.println(I);
System.out.println("abc");
}
public static Test getInstance() { return me; }
public static void main(String[] args) {
Test.getInstance();
}
}
As you can see, the constructor for Test
gets called before I
is initialized. This is why it prints "null"
for I
. If you were to swap the declaration order for me
and I
, you would get the expected result because I
would be initialized before the constructor is invoked. You can also change the type for I
from Integer
to int
.
Because 4
needs to get autoboxed (i.e., wrapped in an Integer
object), it is not a compile-time constant and is part of the static initializer block. However, if the type were int
, the number 4
would be a compile-time constant, so it would not need to be explicitly initialized. Because "abc"
is a compile-time constant, the value of S
is printed as expected.
If you would replace,
public static final String S = "abc";
with,
public static final String S = new String("abc");
Then you would notice the output of S
is "null"
as well. Why does that happen? For the same reason why I
also outputs "null"
. Fields like these that have literal, constant values (that do not need autoboxing, like String
) are attributed with the "ConstantValue"
attribute when compiled, which means that their value can be resolved simply by looking into the class' constant pool, without needing to run any code.