The Java Initializers

In Java, the concepts of classes and instances are the core concepts. A class as well as each instance has variables and methods. To differentiate, the variables and methods corresponding to the class have to be marked static while variables and methods default to the instance (if not marked static). In the following, I focus only on the variables.

The following example shows the declaration for class and instance variables:

public class Variables {
  static String classVariable; // initialized with default value null

  String instanceVariable; // initialized with default value null
}

Class Variables

Class variables are interpreted in the order they appear in the file. I can directly assign values to a class variable (direct initialization) or call a static method which initialises the variable through its return value. This, however, can lead to problems as shown in the next listing:

public class Variables {
  static String directlyInitialized = "class variable";
  static String directlyInitializedWithMethod = init();
  static String anotherDirectlyInitialized = "test";

  private static String init() {
    return anotherDirectlyInitialized;
  }

  public static void main(String[] args) {
    System.out.println(directlyInitializedWithMethod); // prints null
  }
}

This will print out null as the variable anotherDirectlyInitialized is initialized after the directlyInitializedWithMethod is initialized. The compiler does not detect it, it is the responsibility of the programmer to avoid such situations. We could solve this issue by reordering the statements, however, this is an area where we can do errors easily.

There is another alternative, namely, class initializers. These initializers are executed after all static variables have been initialized.

public class Variables {
  static String directlyInitialized = "class variable";
  static String directlyInitializedWithMethod;
  static String anotherDirectlyInitialized = "test";

  static {
    directlyInitializedWithMethod = anotherDirectlyInitialized;
  }

  public static void main(String[] args) {
    System.out.println(directlyInitializedWithMethod); // prints test
  }
}

This static block can contain any complex setup logic. It is executed after all static variables are initialised but before any method call to the class is issued. Good use cases are the computation of constants or preinitialization of other instances which require more than a simple constructor or method call.

Instance Variables

The approach of initializing static variables is also applied to instance variables. I can directly assign values to an instance variable (direct initialization) or call a method which initialises the variable through its return value. This, however, can lead to the same initialization problems.

public class Variables {
  String directlyInitialized = "class variable";
  String directlyInitializedWithMethod = init();
  String anotherDirectlyInitialized = "test";

  private String init() {
    return anotherDirectlyInitialized;
  }

  public static void main(String[] args) {
    InstanceVariables object = new InstanceVariables();
    System.out.println(object.directlyInitializedWithMethod); // prints null
  }
}

However, there is also an initializer construct for instance variables. This construct is called before any constructor is called and allows to initialize instance variables regardless of their ordering.

public class Variables {
  String directlyInitialized = "class variable";
  String directlyInitializedWithMethod;
  String anotherDirectlyInitialized = "test";

  {
    directlyInitializedWithMethod = anotherDirectlyInitialized;
  }

  public static void main(String[] args) {
    InstanceVariables object = new InstanceVariables();
    System.out.println(object.directlyInitializedWithMethod); // prints test
  }
}

Another real use case of these instance initializers is to add values to a HashMap on creation as seen in the next code snippet:

// regular approach
Map<String, String> myMap = new HashMap<>();
myMap.put("DE", "German");
myMap.put("EN", "English");
		
		
Map<String, String> otherMap = new HashMap<String, String>() {
	// anonymous subclass of HashMap

	{
		// instance initializer setting specific values
		this.put("DE", "German");
		this.put("EN", "English");
	}

};

The advantage is, that the latter can be used to initialize a field in a class directly with specific values.