Strange Java behaviour with static and final qualifiers

JavaFinal

Java Problem Overview


In our team we found some strange behaviour where we used both static and final qualifiers. This is our test class:

public class Test {

    public static final Test me = new Test();
    public static final Integer I = 4;
    public static final String S = "abc";

    public Test() {
        System.out.println(I);
        System.out.println(S);
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
} 

When we run the main method, we get a result of:

null
abc

I would understand if it wrote null values both times, since the code of static class members is executed from top to bottom.

Can anyone explain why this behaviour is happening?

Java Solutions


Solution 1 - Java

These are the steps taken when you run your program:

  1. Before main can be run, the Test class must be initialized by running static initializers in order of appearance.
  2. To initialize the me field, start executing new Test().
  3. Print the value of I. Since the field type is Integer, what seems like a compile-time constant 4 becomes a computed value (Integer.valueOf(4)). The initializer of this field has not yet run, printing the initial value null.
  4. Print the value of S. Since it is initialized with a compile-time constant, this value is baked into the referencing site, printing abc.
  5. new Test() completes, now the initializer for I executes.

Lesson: if you rely on eagerly initialized static singletons, place the singleton declaration as the last static field declaration, or resort to a static initializer block that occurs after all other static declarations. That will make the class appear fully initialized to the singleton's construction code.

Solution 2 - Java

S is a compile-time constant, following the rules of JLS 15.28. So any occurrence of S in the code is replaced with the value which is known at compile-time.

If you change the type of I to int, you'll see the same for that, too.

Solution 3 - Java

You have strange behavior due to the Integer data type. Regarding JLS 12.4.2 static fields are initialized in the order you write it, BUT compile-time constants are initialized first.

If you do not use the wrapper type Integer but the int type, you get the behavior you want.

Solution 4 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsrnjakView Question on Stackoverflow
Solution 1 - JavaMarko TopolnikView Answer on Stackoverflow
Solution 2 - JavaJon SkeetView Answer on Stackoverflow
Solution 3 - JavaChristoph W.View Answer on Stackoverflow
Solution 4 - JavaMartin TuskeviciusView Answer on Stackoverflow