How does the static modifier affect this code?

Here is my code:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

The output is 1 0, but I can't understand.

Can somebody explain it to me?


Solution 1:

In Java two phases take place: 1. Identification, 2. Execution

  1. In identification phase all static variables are detected and initialized with default values.

    So now the values are:
    A obj=null
    num1=0
    num2=0

  2. The second phase, execution, starts from top to bottom. In Java, the execution starts from the first static members.
    Here your first static variable is static A obj = new A();, so first it will create the object of that variable and call the constructor, hence the value of num1 and num2 becomes 1.
    And then, again, static int num2=0; will be executed, which makes num2 = 0;.

Now, suppose your constructor is like this:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

This will throw a NullPointerException as obj still has not got a reference of class A.

Solution 2:

What the static modifier means when applied to a variable declaration is that the variable is a class variable rather than an instance variable. In other words ... there is only one num1 variable, and only one num2 variable.

(Aside: a static variable is like a global variable in some other languages, except that its name is not visible everywhere. Even if it is declared as a public static, the unqualified name is only visible if it is declared in the current class or a superclass, or if it is imported using a static import. That's the distinction. A true global is visible without qualification anywhere.)

So when you refer to obj.num1 and obj.num2, you are actually referring to the static variables whose real designations are A.num1 and A.num2. And similarly, when the constructor increments num1 and num2, it is incrementing the same variables (respectively).

The confusing wrinkle in your example is in the class initialization. A class is initialized by first default initializing all of the static variables, and then executing the declared static initializers (and static initializer blocks) in the order that they appear in the class. In this case, you have this:

static A obj = new A();
static int num1;
static int num2=0;

It happens like this:

  1. The statics start out with their default initial values; A.obj is null and A.num1 / A.num2 are zero.

  2. The first declaration (A.obj) creates an instance of A(), and the constructor for A increments A.num1 and A.num2. When the declaration completes, A.num1 and A.num2 are both 1, and A.obj refers to the newly constructed A instance.

  3. The second declaration (A.num1) has no initializer, so A.num1 doesn't change.

  4. The third declaration (A.num2) has an initializer that assigns zero to A.num2.

Thus, at the end of the class initialization, A.num1 is 1 and A.num2 is 0 ... and that's what your print statements show.

This confusing behaviour is really down to the fact that you are creating an instance before the static initialization has completed, and that the constructor you are using depends on and modifies a static that is yet to be initialized. This something that you should avoid doing in real code.

Solution 3:

1,0 is correct.

When the class is loaded all static data is initialized in oder they are declared. By default int is 0.

  • first A is created. num1 and num2 becoming 1 and 1
  • than static int num1; does nothing
  • than static int num2=0; this writes 0 to num2

Solution 4:

It is due to the order of the static initializers. Static expressions in classes are evaluated in a top-down order.

The first to be called is the constructor of A, which sets num1 and num2 both to 1:

static A obj = new A();

Then,

static int num2=0;

is called and sets num2=0 again.

That is why num1 is 1 and num2 is 0.

As a side note, a constructor should not modify static variables, that is very bad design. Instead, try a different approach to implementing a Singleton in Java.

Solution 5:

A section in JLS can be found: §12.4.2.

Detailed Initialization Procedure:

9.Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block, except that final class variables and fields of interfaces whose values are compile-time constants are initialized first

So the three static variable will be initialized one by one in textual order.

So

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

If I change the order to:

static int num1;
static int num2=0;
static A obj = new A();

The result will be 1,1.

Note that the static int num1; is not a variable initializer because(§8.3.2):

If a field declarator contains a variable initializer, then it has the semantics of an assignment (§15.26) to the declared variable, and: If the declarator is for a class variable (that is, a static field), then the variable initializer is evaluated and the assignment performed exactly once, when the class is initialized

And this class variable is initialized when the class is created. This happens first(§4.12.5).

Every variable in a program must have a value before its value is used: Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10): For type byte, the default value is zero, that is, the value of (byte)0. For type short, the default value is zero, that is, the value of (short)0. For type int, the default value is zero, that is, 0. For type long, the default value is zero, that is, 0L. For type float, the default value is positive zero, that is, 0.0f. For type double, the default value is positive zero, that is, 0.0d. For type char, the default value is the null character, that is, '\u0000'. For type boolean, the default value is false. For all reference types (§4.3), the default value is null.