75

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?

double-beep
  • 5,031
  • 17
  • 33
  • 41
srnjak
  • 915
  • 7
  • 21
  • Related http://stackoverflow.com/questions/4446088/java-in-what-order-are-static-final-fields-initialized (and http://stackoverflow.com/questions/2423376/java-initialization-order-issue-static-vs-instance-fields) – Tunaki Sep 12 '16 at 10:31
  • 1
    You can look at the answers for [this question](http://stackoverflow.com/questions/13431388/final-string-vs-final-integer) . hope it helps. – Sagar Singh Sep 12 '16 at 10:38
  • Heh. Another confusion attributable to Java's privileged types. – geometrian Sep 13 '16 at 01:59
  • 1
    Marked as duplicate now… is that the _newsletter effect_? – Didier L Sep 13 '16 at 21:44

4 Answers4

109

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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
71

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
22

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.

Gray
  • 115,027
  • 24
  • 293
  • 354
Christoph W.
  • 958
  • 2
  • 10
  • 24
14

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.

Martin Tuskevicius
  • 2,590
  • 4
  • 29
  • 46
  • 1
    Sure about that? I would expect `System.out.println("abc");`, instead of `System.out.println(S);`, since the Compiler inlines compile time constants. – Tom Sep 12 '16 at 16:32
  • You're right. In the former case there would be an `ldc` instruction rather than a `getstatic`. That's what I was trying to get at with the last sentence, I suppose I should clarify it. – Martin Tuskevicius Sep 12 '16 at 17:06
  • And your code should show that, since you said OPs gets compiled into that. – Tom Sep 12 '16 at 17:43
  • 2
    @Tom That is a distinction without a difference. `S` is another name for `"abc"`. – user207421 Sep 12 '16 at 23:31
  • @EJP Maybe not here, since declaration and access are both in the same class, but it is important to differ both if they are in different classes. – Tom Sep 13 '16 at 18:20