Constructors, sometimes abbreviate to ctor by
habitual text-speakers, are a special sort of method that initialise freshly
minted objects. They are like static methods in
that you don’t need an existing object to use them, just new.
They are like instance methods in that they operate on the current object. They
are a bit like static factory methods in that they
are used in creating new objects, however, they don’t create objects, just
initialise them.
Constructors Are Not Ordinary
Methods
Constructors have a number of magic properties.
- They have the same name as the class.
- Constructors are not inherited. This means you have to write out reams of
dummy constructors for every subclass that just invoke the corresponding super(xxx).
Even if you don’t want a particular constructor, you often have to define
it, just for the benefit of descendant subclasses who may need it as a base for
their constructors.
- If you don’t define the any constructors, Java will generate a
default no-argument constructor for you. You won’t actually see the code
unless you examine the class file with a decompiler.
If you later add some other constructor, Java will no longer continue to
generate this invisible default one. You have to write it out explicitly
yourself.
- You can’t do anything in your constructor before calling super().
If you don’t call super(xxx) or this(xxx)
as the first line of code in a constructor, Java will automatically insert a
call to the default no-argument superclass constructor super()
for you. If the superclass has no default constructor, you will have to insert super(xxx)
manually. Your call to super(xxx) must be the very
first code in the constructor. You can’t even sandwich it in a try
block. The only way you can wiggle around this restriction is to use methods in
the parameters passed to super that have side
effects.
- Constructors logically return an object, yet you are not allowed to declare that
fact. You can’t write void either.
- It is possible to have ordinary static or instance
methods also with the same name as the class. The compiler distinguishes these
from true constructors because they have an explicit return type. Be careful!
- You don’t specify new inside your constructor.
The space for the object has already been allocated before your constructor
started to execute. All you are doing is initialising fields.
- You don’t return this. The constructor does
not return a value. In contrast, a factory static
method that created objects would return a reference to the newly minted object.
- If you wanted to handle your own memory management, e.g. recycle used objects,
you would do this with an ordinary static method,
not a constructor.
- Be very careful calling any methods inside your constructors. If subclasses
override them, you will be invoking the subclass’s version, which may be
attempting to use fields that have not been initialised yet. You won’t get
a warning message! The problem usually shows up as puzzling NullPointerExceptions.
The way out is to move code out of the constructor, often to the addNotify
method. However, addNotify can get in analogous
problem to the constructor since it too is overridden, and it may use overridden
methods. One way to avoid the problem is to use only private
methods in constructor initialisation.
- You can call a constructor only once in an object’s lifetime. Thus the
constructor may initialise fields marked final
where ordinary methods such as addNotify cannot,
even if they are only called once in practice.
Object Initialisation
Objects are initialised in a complex series of steps. However, the process works
pretty much as you expect it would have to. Constructors are just the icing on
the cake in the whole scheme of object initialisation. It works like this:
- At compile time, all the compile time constants are effectively turned into
literals. There is nothing to initialise at run time.
- new allocates space for an object, and zero/nulls
it. The constructor code initialises it.
- All the fields are zeroed. It is highly convenient that 0 is the default value
for int, char, null
reference etc. The entire object can be bulk-zeroed with a single assembler Fill-with-zeros
instruction.
- The constructor is started. The very first thing it does is call its superclass
constructor whose job is to initialise fields in the class this one was derived
from. That constructor in turn first calls its superclass constructor up the
chain to Object’s constructor. The newly
minted object effectively then gets initialised by a chain of constructors
starting with the most basic Object. Note that no
initialisation of any kind has been done of this class’s fields. If the
superclasses use any methods we override, they may get a nasty surprise finding
uninitialised fields.
- Eventually the chain of constructors returns.
- Fields that were initialised inline (with an = on the declare) are initialised
mixed in with the fields that were initialised inside { } initialiser blocks.
Assignment declare initialisation and init blocks are done in the exact order
top to bottom they appear in the code. The JVM does not do the assignment
declare style initialisations first. If you decompile a constructor, you will
see your constructors have method names like: MyClass.
"<init>":()V You can see them call
the superclass constructor, do the instance initialisers and finally the
constructor procedural code.
- Finally the constructor gets its chance to initialise its own fields (or modify
derived ones.) with procedural code. You have to be aware of this initialisation
order. Java is not smart enough to resolve initialisation dependencies. I got in
trouble with this when I ran a code beautifier that put all my fields in
alphabetical order. The dependencies no longer worked and the fields were
initialised to the wrong values, without any warning messages.
addNotify
Don’t put GUI-type code in your constructor. Put it in an addNotify
method that looks like this:
/**
* Build the GUI
*/
public void addNotify()
{
super.addNotify();
setTitle( "Whazmotron Inc." );
...
}
Don’t forget to call super.addNotify
(); first, or the peer object won’t
get created and your Component won’t be hooked
into the actual underlying native GUI and you will be staring at a blank screen.
Putting GUI code here means the GUI is ready for it. The peer exists as soon at
the code returns from the call to super.addNotify(),
not before. the peer does not yet exist in the constructor. You need a peer for getImage
type activities. Further, if you call methods in your constructor that
subclasses override, it means you are less likely to be referencing variables
that don’t exist yet if you divide your initialisation logically between
the constructor and addNotify.
This is also a good place to move constructor code that is failing because it
uses methods overridden my subclasses that reference fields not yet initialised
by the subclass’s constructor which has not yet run yet.