Objectos Weekly #011: Instance initializers in Java
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:
-
before or after the instance or the superclass constructor?
-
before or after the instance fields are initialized?
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:
-
after the superclass constructor execution; and
-
before the statements declared in the instance constructor.
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:
-
First, the superclass constructor is executed;
-
Next, the instance and field initializers are executed in the order they appear in the source code; and
-
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.