6

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?

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
  • 4
    **Yes**. I don't know that it's **guaranteed** to work, your code has to be defect free; and the JVM would have to be defect free (and your host Operating System and computer). It would also be **legal** to write `private final static int bar = generateValue();` – Elliott Frisch Mar 13 '16 at 23:23

3 Answers3

5

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).

Austin
  • 8,018
  • 2
  • 31
  • 37
  • I don't understand how this work ```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); } }``` is it because it is a flaw in java language when it was designed? – sofs1 Mar 21 '19 at 22:49
  • 1
    @sofs1 It's is a feature of Java, not a flaw. It's motivated by the fact that maintaining textual ordering can sometimes be an onerous task. In practice, I have rarely ever run into issues with textual ordering, but developers should be aware of the fringe cases highlighted above since the bugs they introduce are especially insidious – Austin Mar 23 '19 at 00:55
3

In a word - yes. This is perfectly legal code, and should [statically] initialize bar to 123.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
1

Static initializer is really unnecessary here and I wouldn't use it. You can do

private final static int bar = generateValue();

even if the generateValue() method is defined after the static member (and I just tried it to be sure).

In my book, static initializers are only necessary for complicated initializations or when the initializer can throw an exception. For example, this isn't going to work

private final InetAddress inet = InetAddress.getByName ("some bad host name");

because an exception can be thrown. You also need to use a static initializer if there's if-then-else logic to deal with, perhaps a temporary variable is needed, or anything else that isn't a straight assignment.

But for what you have here, a static initializer block is totally extraneous and, in my opinion, not a best practice to emulate.

Scott Sosna
  • 1,443
  • 1
  • 8
  • 8