Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a Java static initializer call a static method?

Can I call a static method from a static initializer in Java? Is the following valid and guaranteed to work as per the Java specification?

public class Foo {

  private final static int bar;

  private static int generateValue() {
    return 123;
  }

  static {
    bar = generateValue();
  }

}

What makes me wonder is that I might expect bar to be available inside generateValue(). I know the order of static initializer blocks is important, but I hadn't heard of the order of static method declarations being significant. But are static methods made available before static initializer blocks are executed?

like image 658
Garret Wilson Avatar asked Mar 13 '16 23:03

Garret Wilson


1 Answers

As @Mureinik said, "In a word -yes. This code is perfectly legal." I want to provide a more thorough answer because you can easily develop problems when using static initializers in conjunction with class methods--the order of appearance can affect the class state, and it's a nasty bug to track down.

A static initializer declared in a class is executed when the class is initialized. Together with any field initializers for class variables... static initializers may be used to initialize the class variables of the class -- Java Language Specification (JLS) §8.7

Initialization generally takes place in the order of appearance (called textual ordering). For example, consider the following code:

class Bar {
    static int i = 1;
    static {i += 1;}
    static int j = i;
}

class Foo {
    static int i = 1;
    static int j = i;
    static {i += 1;}

    public static void main(String[] args) {
        System.out.println("Foo.j = " + Foo.j);
        System.out.println("Bar.j = " + Bar.j);
    }
}

The value for Foo.j and Bar.j are different because of the differences in the textual ordering of the code:

Foo.j = 1
Bar.j = 2

The OP's example executes in textual order. But what if the code were rearranged, say, in reverse order:

class Foo {
    static { bar = generateValue(); }                  //originally 3rd
    private static int generateValue() { return 123; } //originally 2nd
    private final static int bar;                      //originally 1st

    public static void main(String[] args) {
        System.out.println("Foo.bar = " + Foo.bar);
    }
}

It turns out, that this compiles without error. Furthermore, the output is: Foo.bar = 123. So, bar does in fact contain 123 at runtime. However, the following code (from JLS §8.3.1.1) produces a compile time error because it tries to access j before j is declared:

//Don't do this!
class Z { 
    static { i = j + 2; } //Produces a compilation error
    static int i, j;
    static { j = 4; }
}

Interestingly, accesses by methods are not checked in this way, so:

class Foo {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;

    public static void main(String[] args) {
        System.out.println("Foo.i = " + Foo.i);
    }
}

produces the following output:

Foo.i = 0

this happens because

the variable initializer for i uses the class method peek to access the value of the variable j before j has been initialized by its variable initializer, at which point it still has its default value -- JLS §8.3.2.3

If instead, i is initialized after j, then the output is

Foo.i = 1

And the situation gets worse when using objects instead of primitive types, as in:

class Foo { //Don't do this
    static int peek() { return j.hashCode(); } // NullPointerException here
    static int i = peek();
    static Object j = new Object();

    public static void main(String[] args) {
        System.out.println("Foo.i = " + Foo.i);
    }
}

This peek throws a NullPointerException while initializing i:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
    at TestGame.Foo.peek(Foo.java:4)
    at TestGame.Foo.<clinit>(Foo.java:5)

Eclipse pops-up this window when the above code is run:

Java Exception has occurred

Tying this back to the OP, if instead of returning 123, the generateValue() method returned the value of some other static field (or method), then the value of bar depends on the textual ordering of the code.

So, when is textual ordering important?

Textual ordering is not always used. Sometimes the JVM looks-ahead to perform initialization. It's important to know when look-ahead is possible, and when initialization occurs in textual order. The JLS describes the Restrictions on the use of Fields during Initialization in §8.3.2.3 (emphasis my own):

The declaration of a member needs to appear textually before it is used only if the member is [a] ...static field of a class or interface C and all of the following conditions hold:

  • The usage occurs in [a]... static variable initializer of C or in [a] ...static initializer of C.
  • The usage is not on the left hand side of an assignment.
  • The usage is via a simple name.
  • C is the innermost class or interface enclosing the usage.

One last note: constants are initialized first (per JLS §8.3.2.1):

At run time, static fields that are final and that are initialized with constant expressions (§15.28) are initialized first (§12.4.2).

like image 165
Austin Avatar answered Sep 20 '22 13:09

Austin