Strange Java behavior with static and final qualifiers

In our team, we found some strange behavior in which we used the 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 the result:

 null abc 

I would understand if he wrote null values ​​both times, since the code of the static members of the class runs from top to bottom.

Can anyone explain why this behavior occurs?

+76
java final order
Sep 12 '16 at 10:22
source share
4 answers

These are the steps taken when starting your program:

  • Before starting main the Test class must be initialized by starting the static initializers in the order they appear.
  • To initialize the me field, start the execution of new Test() .
  • Print the value of I Since the type of the field is Integer , what looks like a compile-time constant of 4 becomes a computed value ( Integer.valueOf(4) ). The initializer for this field is not yet running by typing an initial null value.
  • Print the value of S Since it is initialized with a compile-time constant, this value is baked on the link site by printing abc .
  • new Test() completes, now the initializer for I is executed.

Lesson: if you rely on impatiently initialized static singletones, place a singleton declaration as the last declaration of a static field or resort to the static initializer block that occurs after all other static declarations. This will cause the class to be fully initialized to a single building code.

+109
Sep 12 '16 at 10:28
source share

S is a compile-time constant, following the rules of JLS 15.28 . Therefore, any appearance of S in the code is replaced by a value that is known at compile time.

If you change type I to int , you will see the same too.

+71
Sep 12 '16 at 10:25
source share

You have weird behavior due to the Integer data type. Regarding JLS 12.4.2, static fields are initialized in the order in which you write it, BUT the compilation constants are initialized first.

If you are not using the Integer shell type, but the int type, you will get the desired behavior.

+21
Sep 12 '16 at 10:38 on
source share

Your Test compiles to:

 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 is called before I initialized. That is why it prints "null" for I If you had to change the declaration order for me and I , you will get the expected result, because I will be initialized before the constructor is called. You can also change the type for I from Integer to int .

Since 4 needs to get an autobox (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 was int , the number 4 would be a compile-time constant, so it would not have to be explicitly initialized. Since "abc" is a compile-time constant, the value of S is output as expected.

If you replace,

 public static final String S = "abc"; 

from,

 public static final String S = new String("abc"); 

Then you will notice that the output of S is "null" . Why is this happening? For the same reason why I also prints "null" . Fields like these, which have literal, constant values ​​(autoboxing, for example String , are not required), the "ConstantValue" attribute when compiling, which means that their value can be resolved simply by searching in a constant pool of classes, without having to run any either code.

+13
Sep 12 '16 at 15:41
source share



All Articles