Objectos Weekly #011: Instance initializers in Java

Marcio Endo
January 29, 2023

Welcome to Objectos Weekly issue #011.

In this issue, we will continue our discussion about blocks in Java.

Apart from fields, methods, constructors and nested types, classes can also declare instance and static initializers. Syntactically speaking, the last two are blocks.

In this issue we will focus on instance initializers. We might revisit static initializers at a later time.

Let's begin.

Instance initializers

What are instance initializers? They are blocks declared in the body of a class. Here's an example:

public class InstanceInitializer {

  {
    System.out.println(
      """
      Hi, I am an instance initializer.

      I am executed when an instance of the class is created.

      But when exactly?

      That's what we'll discuss in this article.
      """
    );
  }

}

See how the block is declared directly in the body of the class? In other words, the block is not inside a constructor nor a method. It is a direct child of the class if you will.

These blocks are formally called instance initializers.

When are instance initializers executed?

I don't think I have ever used instance initializers. So I know very little about them. In particular, when is an instance initializer executed?

In other words, when a new instance of a class is created, is the instance initializer executed:

This is what we'll investigate.

Our Create class

As a baseline, we will start with this Create class:

public class Create {
  public Create() {
    echo("Create constructor");
  }

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

  private void echo(String msg) {
    System.out.println(msg);
  }
}

It has a main method. So when we execute it as a Java program it prints:

Create constructor

OK, on to the next step.

Our instance initializer

If we add an instance initializer, will it run before or after the constructor? Here's our modified Create class:

public class Create {
  {
    echo("Initializer");
  }

  public Create() {
    echo("Create constructor");
  }

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

  private void echo(String msg) {
    System.out.println(msg);
  }
}

This iteration prints:

Initializer
Create constructor

Interesting. It seems the initializer is run before the first statement of the constructor.

But remember that the Create constructor implicitly invokes the superclass constructor. So let's keep investigating.

Adding a Super class

Let's create a superclass for our Create class. We'll call it Super:

abstract class Super {
  public Super() {
    echo("Super constructor");
  }

  final void echo(String msg) {
    System.out.println(msg);
  }
}

We have also moved the echo method to the Super class.

Next, let's have the Create class extend our newly defined Super class:

public class Create extends Super {
  {
    echo("Initializer");
  }

  public Create() {
    echo("Create constructor");
  }

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

When executed it prints:

Super constructor
Initializer
Create constructor

Wait, what?

So the instance initializer is executed:

What if we add field initializers to the mix?

Adding one field

So let's modify our Create class:

public class Create extends Super {
  {
    echo("Instance initializer");
  }

  final String fieldA = initA();

  public Create() {
    echo("Create constructor");
  }

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

  private String initA() {
    echo("Field A initializer");
    return "A";
  }
}

We have added a String field called fieldA. It is a final field whose value is given by evaluating the initA method.

Let's run the program. It prints:

Super constructor
Instance initializer
Field A initializer
Create constructor

Both the instance and the field initializers were executed before the statements in the instance constructor. Additionally, in this particular run, the field initializer was executed after the instance initializer.

Is this a general rule? Spoiler alert: no. Let's investigate it further.

Adding a second field

Let's add a second field to our Create class:

public class Create extends Super {
  final String fieldB = initB();

  {
    echo("Instance initializer");
  }

  final String fieldA = initA();

  public Create() {
    echo("Create constructor");
  }

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

  private String initA() {
    echo("Field A initializer");
    return "A";
  }

  private String initB() {
    echo("Field B initializer");
    return "B";
  }
}

We have added a field called fieldB before the instance initializer. The fieldA remains after the instance initializer.

Let's execute our example. It prints:

Super constructor
Field B initializer
Instance initializer
Field A initializer
Create constructor

So it seems that field and instance initializers are run in the order they are found in the source code.

Perhaps we should have started by reading the Java Language Specification (JLS), where this is all defined.

It's all in the JLS

What happens when a new class instance is created is explained in Section 12.5 of the JLS.

It lists a procedure of 5 steps that must be observed when creating a new instance of a class. Or, in JLS wording, the procedure that must be followed "just before a reference to the newly created object is returned as the result".

Step 1: the constructor arguments are evaluated.

Step 2: constructors which invoke another constructor in the same class using this are handled.

Step 3: the superclass constructor is invoked. So in our last example the "Super constructor" output happens in this step.

Step 4: instance initializers and field initializers are executed. Here's an excerpt of this step from the JSL (emphasis mine):

Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class.

So in our last example, as specified, they were executed in the order they appear in the source code.

Step 5: the rest of the body of the constructor is executed. This is the step where the "Create constructor" output happens.

Putting it all together

Just to summarize our findings, here is the order of the execution:

  1. First, the superclass constructor is executed;

  2. Next, the instance and field initializers are executed in the order they appear in the source code; and

  3. Finally, the rest of the body of the constructor is executed.

No return statements

There is a caveat when using instance initializers: return statements are not allowed.

The following class does not compile:

public class Return {

  {
    int value = random();

    if (value < 0) {
      // does not compile!!!
      return;
    }

    compute(value);
  }

  private void compute(int value) {
    // do computation
  }

  private int random() {
    return ThreadLocalRandom.current().nextInt();
  }

}

Compilation fails with the following message:

$ javac -d /tmp src/main/java/post/Return.java 
src/main/java/post/Return.java:16: error: 
return outside method
      return;
      ^
1 error

So there's that.

Interaction with Archie Cobbs' work

As a final note, know that Archie Cobbs is working on a JEP draft whose title is:

No longer require super() and this() to appear first in a constructor

One of the ideas in the JEP is to allow the following:

public class PositiveBigInteger extends BigInteger {
  public PositiveBigInteger(long value) {
    if (value <= 0)
      throw new IllegalArgumentException("non-positive value");
    super(value);
  }
}

See how there's an if statement before the superclass constructor invocation?

To be honest, I haven't looked at the JEP details yet. Nor have I read the ongoing discussion in the amber-dev mailing list. But it seems this might interact with instance initializers.

Let's work together

Do you feel that adding features to your Java applications is taking longer as time goes by? Perhaps I can help. Let's get in touch.

You can find my contacts on this page. All work will be provided via Objectos Software LTDA based in São Paulo, Brazil.

Until the next issue of Objectos Weekly

So that's it for today. I hope you enjoyed reading.

The source code of all of the examples are in this GitHub repository.

Please send me an e-mail if you have comments, questions or corrections regarding this post.