Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange Java Behavior Regarding Interfaces and Class Circular Dependency

Tags:

java

class

I have the following code:

public class Main {
    public static void main(String[] args) {
        System.out.println("Class.INSTANCE = " + Class.INSTANCE);
        System.out.println("Interface.INSTANCE = " + Interface.INSTANCE);
    }
}

interface Interface {
    Interface INSTANCE = Class.INSTANCE;

    default void func() { }
}

class Class implements Interface {
    public static final Class INSTANCE = new Class();
}

I run it using Java 17.0.7, java Main.java I get:

Class.INSTANCE = Class@5579bb86
Interface.INSTANCE = null

It looks like the Interface.INSTANCE is not initialized properly. If I swap two printlns, I get the expected result:

Interface.INSTANCE = Class@5579bb86
Class.INSTANCE = Class@5579bb86

Also If I remove the default method in Interface (from the first code snippet) everything works fine as well and no null is printed.

What is happening here? More specifically I have three questions:

  1. Why does the first code snippet prints null?
  2. Why don't the second and third code snippet print null?

I know it probably has something to do with circular dependencies, but I want to know what happens exactly at the JVM level.

P.S. Question title suggestions are welcome

like image 379
ParSal Avatar asked Jan 21 '26 14:01

ParSal


1 Answers

This is specified in the JLS (§9.3.1, §12.4.1):

Initialization of Fields in Interfaces

At run time, the initializer is evaluated and the field assignment performed exactly once, when the interface is initialized.

When Initialization Occurs

When a class is initialized, its superclasses are initialized (if they have not been previously initialized), as well as any superinterfaces that declare any default methods (if they have not been previously initialized).

It specifically says "superinterfaces that declare any default methods" because those default methods can be called from the instance constructor:

class Class implements Interface {
    public static final Class INSTANCE = new Class();
    Class() {
        func(); // this default method could use the interface's static fields!
    }
}

According to the Detailed Initialisation Procedures found in the next section, the interfaces are actually initialised before the field initialisers in the class are executed (See step 7 and step 9).

  1. Next, if C is a class rather than an interface, then let SC be its superclass and let SI1, ..., SIn be all superinterfaces of C that declare at least one default method.

    For each S in the list [ SC, SI1, ..., SIn ], if S has not yet been initialized, then recursively perform this entire procedure for S

  1. Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.

Furthermore, if the class is in the middle of being initialised, nothing should be done (see step 3).

  1. If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.

So when you first access Class.INSTANCE, Class is initialised. In this process, Interface is first fully initialised (because it has a default method). At this point Class.INSTANCE is not initialised yet, hence assigning null to INSTANCE. Then, the field initialisers in Class gets executed.

If you first access Interface.INSTANCE, Interface gets initialised and its field initialisers run. The field initialiser for Interface.INSTANCE accesses Class.INSTANCE for the first time, and so causes the initialisation of Class. This causes Class.INSTANCE to be assigned a non-null value. Remember that we are still running the field initialisers of Interface! After the full initialisation of Class, Interface.INSTANCE gets the non-null value.

like image 191
Sweeper Avatar answered Jan 24 '26 02:01

Sweeper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!