I recently read the Oracle documentation on “Initializing Instance Members”. The article talks about the three most common ways of member initialization being assignment in the constructor, using initializer blocks and using final methods which are called from the constructor.
Why just final methods? Well, as stated in the article:
A final method cannot be overridden in a subclass.
Ok, but why shouldn’t we override these initialization methods? After all I often see code like this:
They then go on to say:
“Calling non-final methods during instance initialization can cause problems. Joshua Bloch describes this in more detail in Effective Java”
Now, I assume that like me, when people are reading this page, usually at work, they generally don’t have their copy of Effective Java with them. So, I’m going to try my best to explain this so that its available via a simple google search.
Let’s look at a simple example. We have a Duck and at design time we decide to make the assumption that when Ducks speak they all say “quack”. At some point in the future we decide to add a SqueakyDuck; which when speaking says “squeak”. The code for this is below:
Disclaimer: Now, I know this is a bad example and there are better ways to achieve this behaviour, such as the Strategy Pattern, but that is a post for another day. Nevertheless, I feel this general pattern occurs in practice quite a lot.
The problem arises when we run the following code:
and we get the following output:
Hi, I’m a Squeaky Duck, and I say null
Chapter 12.5 “Creation of New Class Instances” of the Java Language Specification helps to clarify why this occurs (relevant bits highlighted below):
Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:
Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
If this constructor begins with an explicit constructor invocation of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.
This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.
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. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5. (In some early implementations, the compiler incorrectly omitted the code to initialize a field if the field initializer expression was a constant expression whose value was equal to the default initialization value for its type.)
Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.
This means that on execution of the SqueakyDuck constructor, it in turn calls super(), which in turn calls the overridden initSpeech() method, inside the SqueakyDuck class, before the SqueakyDuck instance members have been fully initialized. This means that the when initSpeech() is run the squeakySound field has the default value, which is null for all objects. However, once the SqueakyDuck object is fully initialized the squeakySound field is set to “squeak” which is why the call to getSound() returns “squeak”.