2

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

Community
  • 1
  • 1
lukas.coenig
  • 541
  • 1
  • 7
  • 19
  • What's your exact definition of "program start", when the first line of `main` gets executed? (There could, of course, be code running before then, including before all the static initializers have run.) – NPE Dec 28 '14 at 10:35
  • I suggest you use a debugger to see how the initialization occurs, if you can't provide a minimal reproducing example. – M A Dec 28 '14 at 10:37
  • I think, my misunderstanding might have been quite simple after all. Eddie B wrote, if a class is not loaded, the according static fields are not initialized as well. If this is true, it would explain my observed behavior. I will look into it! – lukas.coenig Dec 28 '14 at 10:41
  • 1
    Well, yes, the JVM won't even know what static variables there are in a class if it doesn't load the class. So anything relating to the static variables can only happen after the class is loaded. – Dawood ibn Kareem Dec 28 '14 at 10:47
  • Yes, this was the problem. Thank you all for the support - I learned a lot! (Although the answer was quite obvious in the end.) – lukas.coenig Dec 28 '14 at 10:57

5 Answers5

4

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

Community
  • 1
  • 1
Edward J Beckett
  • 5,061
  • 1
  • 41
  • 41
2

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) {
    }
}
NPE
  • 486,780
  • 108
  • 951
  • 1,012
1

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.

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

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • I am not sure how to read the code... Is this the raw byte code? (Still, I believe, if a null initialization were the problem, I would receive an exception rather than the described behavior.) – lukas.coenig Dec 28 '14 at 10:43
  • 2
    @lukas.coenig This is the dump of the byte code from `javap`. You would get ExceptionInInitializerError if this class failed to initialise due to a previous error. You need to look back through your errors/exceptions to find the true cause. – Peter Lawrey Dec 28 '14 at 10:53
1

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)    
dognose
  • 20,360
  • 9
  • 61
  • 107
  • Yeah, but this is exactly what I did NOT get - and why I got confused... But I solved it now, see my comments above. – lukas.coenig Dec 28 '14 at 10:59
1

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");}};
Artur
  • 7,038
  • 2
  • 25
  • 39
  • Yup, that's exactly what the problem was... Thanks! – lukas.coenig Dec 28 '14 at 11:01
  • But - if ACCESS causes initialization - how could it happen to access uninitialized values? :) – dognose Dec 28 '14 at 11:02
  • @dognose: if you do not initialize class's of obj's field it gets initialized to its default value which in this particular case (int) will be 0. If you do not try to 'print' 'a' initialisation will not happen. – Artur Dec 28 '14 at 11:08