简体   繁体   中英

Are there circumstances under which Java does not initialize static fields immediately?

In a bigger project I am experiencing a strange behavior (at least for my understanding) with static field initialization. As I understand it, all static fields should be initialized at program start, meaning that when starting to work with a non-static field, there should not be any non-initialized static fields (more precisely, all static assignments "field = ..." should have been performed).

The following code is NOT a MWE, because it DOES what I expect it to, but it is essentially exactly what I'm doing in the bigger context. I have not been able to create a smaller example which results in the same problem.

When running this code:

import java.util.HashSet;

public class FB {
    private static final HashSet<String> collection = new HashSet<>();
    public static final String foo = bar("item");

    public static String bar(String newItem) {
        collection.add(newItem);
        System.out.println("Yes, I've been invoked, and I currently store this: " + collection);
        return newItem;
    }

    public static void main(String[] args) {
    }
}

the output is (as Java initializes first the static field 'collection' and then foo by invoking bar(.)):

Yes, I've been invoked, and I currently store this: [item]

So far, so good. In the real project, I am doing exactly this (although foo and bar(.) are in different classes), but bar(.) is NOT invoked before I actually use the value of foo. (At least this happens in one case out of five - which are all created in the same way as shown above. The other four work fine.) Are there any circumstances which would cause Java behaving like this?

I already looked at these discussions, but they do not seem to capture my problem:

When are static variables are initialized?
Why static fields are not initialized in time?
Java Static Field Initialization

I realize that, when swapping the positions of foo and collection, the method invokation cannot work because collection would not be initialized (or rather, initialized to null?) when foo gets initialized. (To be honest, I am not sure in which order static fields are initialized when located in different classes, so this might be a source of problems.) But this would result in a

Exception in thread "main" java.lang.ExceptionInInitializerError

and not in just not invoking bar(.).

If required, I can provide more details about the real project, but so far I don't know what else might be of interest. (Sorry for the fuzzy description, but this is all I have so far.)

Static variables are instantiated by the JVM classloader and are shared by every instance of the class.

public class StaticVars {

    static int i = 3;

    public static void main( String[] args ) {
        System.out.println( "Called at Runtime: " + getStaticVar() );
        System.out.println( "Called via it's static member: " + i );
    }


    static int getStaticVar() {
        return i;
    }

    static {
        int i = 1;
        System.out.println( "JVM ClassLoaded: " + i );
    }

    static {
        int i = 2;
        System.out.println( "Second JVM ClassLoaded: " + i);
    }

}

Further, as dognose and NPE referenced, if you attempt to reference a static variable before it is initialized you will get an Illegal Forward Reference Error as static fields are initialized in sequential order. The following restrictions apply to static fields static methods are not checked in the same manner.

8.3.2.3. Restrictions on the use of Fields during Initialization

  • The declaration of a member needs to appear textually before it is used only if the member is an instance (respectively static) field of a class or interface C and all of the following conditions hold :

  • The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively 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.

More Info :: Illegal forward reference error for static final fields

when starting to work with a non- static field, there should not be any non-initialized static fields (more precisely, all static assignments field = ... should have been performed).

Generally, that's the case. However, it is possible to construct an example where it isn't. The following code prints out

static_obj=null
instance_obj=4

meaning that static_obj has not been initialized by the time instance_obj has been:

class Ideone
{
    static {
        new Ideone();
    }   

    public static final Object static_obj = new Integer(42);
    public final Object instance_obj = new Integer(4);

    public Ideone() {
        System.out.printf("static_obj=%s\n", static_obj);
        System.out.printf("instance_obj=%s\n", instance_obj);
    }

    public static void main(String[] args) {
    }
}

If you compile the class above, you can see that bar is called in the static initialization block.

static <clinit>()V
  L0
    LINENUMBER 4 L0
    NEW java/util/HashSet
    DUP
    INVOKESPECIAL java/util/HashSet.<init> ()V
    PUTSTATIC FB.collection : Ljava/util/HashSet;
  L1
    LINENUMBER 5 L1
    LDC "item"
    INVOKESTATIC FB.bar (Ljava/lang/String;)Ljava/lang/String;
    PUTSTATIC FB.foo : Ljava/lang/String;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

The only way to see an incomplete initialised class in FB is to be in the static block already.

eg say FB.<clinit>() calls another class which in turn uses FB.foo before it is initialised, it will see null rather than call bar()

Static fields are initialized in the order they are declared. So, if you would change the order of your fields to this:

import java.util.HashSet;

    public class FB {
        public static final String foo = bar("item");
        private static final HashSet<String> collection = new HashSet<>();

        public static String bar(String newItem) {
            collection.add(newItem);
            System.out.println("Yes, I've been invoked, and I currently store this: " + collection);
            return newItem;
        }

        public static void main(String[] args) {
        }
    }

you will receive a NullpointerException inside the bar -method, when trying to access the not yet initialized collection. (Because your call to bar() happens in the progress of initializing the first variable)

Exception in thread "main" java.lang.ExceptionInInitializerError                                

Caused by: java.lang.NullPointerException                                                       

        at HelloWorld.bar(HelloWorld.java:14)                                                   

        at HelloWorld.<clinit>(HelloWorld.java:10)    

Static fields and static init blocks of a class are not always initialised/executed. If you do not use a class or do not load it it will not happen. For example - suppose you have:

class Test
{
    static
    {
        System.out.println("Test");
    }

    public static int a = 4; 
}

If you do not use Test class static block will not be executed. So you must for example instantiate a class to init static fields and invoke static init blocks:

Test t = new Test();

or use anything that is static like:

System.out.println(Test.a); // this will trigger 'initialisation'

After all - if you do not use static data in your code - why would JVM bother doing anythingh with it - deem it reasonable optimisation ;-).

Another example of using JDBC - you must call this once to let driver 'init' itself - ie. execute everything that is static and will be later needed:

Class.forName( "com.mysql.jdbc.Driver" ).newInstance();

BTW: you may init your collection this way:

private static final HashSet<String> collection = new HashSet<String>() {{add("item");}};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM