1

Possible Duplicate:
Java Enums: Two enum types, each containing references to each other?

In our code we got some weird NPE's concerning Enums. When I searched, I found (more or less) the following case:

public class EnumTest {

  public static void main(final String[] args){
    System.out.println("------ START ----- ");
    System.out.println("BeezleBubs FOO's rockSteady is: " + BeezleBub.FOO.rockSteady);
    System.out.println("RockSteady BAR's beezleBub is: " + RockSteady.BAR.beezleBub);
    System.out.println("------  END  ----- ");
  }

  public enum RockSteady {
    BAR(BeezleBub.FOO);

    public final BeezleBub beezleBub;
    private RockSteady(final BeezleBub beezleBub) {
        this.beezleBub = beezleBub;
        System.out.println("Constructing RockSteady, beezleBub = " + beezleBub);
    }

  }

  public enum BeezleBub {

    FOO(RockSteady.BAR);

    public final RockSteady rockSteady;

    private BeezleBub(final RockSteady rockSteady) {
        this.rockSteady = rockSteady;
        System.out.println("Constructing BeezleBub, rockSteady = " + rockSteady);
    }

  }
}

For some reason the results are awkward. When run, this test outputs:

------ START ----- 
Constructing RockSteady, beezleBub = null
Constructing BeezleBub, rockSteady = BAR
BeezleBubs FOO's rockSteady is: BAR
RockSteady BAR's beezleBub is: null
------  END  ----- 

The other thing is that when you switch the System.out.prinln() statements calling the Enums, the initialization of the enums change as well. Resulting in:

------ START ----- 
Constructing BeezleBub, rockSteady = null
Constructing RockSteady, beezleBub = FOO
RockSteady BAR's beezleBub is: FOO
BeezleBubs FOO's rockSteady is: null
------  END  ----- 

Anyone has a clear explanation of what is happening? It has something to do with state & order, but I can't quite put my finger on it...

Community
  • 1
  • 1

3 Answers3

3

In Java, classes are loaded and initialized lazy. That means, whichever class's attribute you try to print first is loaded and initialized first. In general, if you have mutually recursive initializer of classes, then you should avoid to inspect their attributes before all constructors are finished.

The reason for that is simply that there is no sequence of initialization steps Java could perform which would ensure atomic initialization for all possible uses.

Btw, this has nothing to do with enums it can happen with plain old Java classes.

jmg
  • 7,308
  • 1
  • 18
  • 22
  • The linked dupe by Tim had a similar explanation - both sound logical: "I understand why it happens - the JVM starts classloading Foo; it sees the Bar.Alpha in Foo.A's constructor, so it starts classloading Bar. It sees the Foo.A reference in the call to Bar.Alpha's constructor, but (since we're still in Foo.A's constructor) Foo.A is null at this point, so Bar.Alpha's constructor gets passed a null. If I reverse the two for loops (or otherwise reference Bar before Foo), the output changes so that Bar's values are all correct, but Foo's values are not." –  Nov 10 '11 at 09:00
0

Cyclic dependencies are not cool, and I must admit I did not produced such code yet. I can guess, that instances enum instances initialized on demand, and first instance pocked pulls final value which is still null ( final means that you can not reassign value, not that it was already assigned ). When you poke second enum, first was already created and eveything works as expected

Konstantin Pribluda
  • 12,329
  • 1
  • 30
  • 35
0

The first println statement needs the value of BeezleBub.FOO.rockSteady. So the BeezleBub enum is loaded. To initialize the BeezleBub FOO instance, it needs to load the RockSteady enum. So RockSteady is loaded, and the BAR constant is initialized to the current value of BeezleBub.FOO, which is still null since it's in the process of initializing itself. Then the initialized BeezleBub.FOO constant is assigned the value of RockSteady.BAR, which is non null.

It's an interesting problem, because it's one of the cases where one of the enum should be mutable in order to be properly initialized.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Reading your comments triggered me that the testcase is somewhat confusing as BeezleBub.FOO.rockSteady is called before the sysout completed. Giving the impression that the enum is actually instantiated before anything else... –  Nov 10 '11 at 08:46