The Canonical Constructor
An additional constructor is any constructor in a class other than the canonical constructor, and it gives callers an alternative way to initialize an object.
Why One Name Is Not Enough
Imagine a FullName class that normally stores two pieces of information: a first name and a last name. Most people have both, so the usual way to create an object takes both values. But some people go by only one name, and a program that models people should still be able to represent them. This is the problem additional constructors solve. They give a class more than one way to be built, each suited to a different kind of input.
In this article you will trace a small constructor chain step by step, predict which fields end up holding which values, and then see how the main constructor can be customized to reject bad input. Try to reason about each prediction yourself before reading the explanation that follows it.
Additional Versus Canonical Constructors
A canonical constructor takes parameters to initialize an object's fields, and it can be customized with additional code.
An additional constructor is any constructor in a class other than the canonical constructor, and it gives callers an alternative way to initialize an object.
The two kinds of constructor differ in what they accept, and in who does the final setting of the fields. The canonical constructor takes the full set of values and assigns them to the fields. An additional constructor takes a different set of inputs, and its job is to get those inputs into a form the canonical constructor can use. In the FullName example, the canonical constructor takes two parameters, a first name and a last name, while the additional constructor takes only one.
Tracing a One-Name Call
- The record header declares the two components,
firstNameandlastName, which the canonical constructor sets. - The one-parameter constructor is an additional constructor because it is not the canonical one.
this(onlyName, null)selects the two-parameter constructor and supplies the arguments.
Suppose a caller writes new FullName('Ada'). The one-parameter constructor runs first, with onlyName holding the value 'Ada'. Its only statement is this(onlyName, null). The keyword this followed by parentheses refers to a constructor of the same class rather than to a method or a field. Here it selects the two-parameter constructor, which is the canonical one in this class, and passes it two arguments: 'Ada' and null.
Control now moves to the canonical constructor. That constructor sets the firstName field to the value of its first argument, which is 'Ada', and the lastName field to its second argument, which is null. When it finishes, control returns to the one-parameter constructor, which has nothing left to do. The result is an object whose firstName is 'Ada' and whose lastName is null.
Constructor Chaining and Shared Assignments
Notice that the one-parameter constructor never assigns either field directly. It delegates that assignment entirely to the canonical constructor. This is why the pattern is called constructor chaining: one constructor calls another, which in turn may do the real work. Delegating keeps the field assignments in one place, which reduces duplication and makes later changes safer. If the canonical constructor later needs to set a field differently, that change is made once, and every additional constructor that chains to it benefits.
| Question | Canonical constructor | Additional constructor |
|---|---|---|
| What does it accept? | A value for every field, such as firstName and lastName | A different set of inputs, such as only onlyName |
| What does it delegate to? | Nothing; it is the destination of delegation | The canonical constructor, through this(...) |
| Which one sets the fields? | It does, directly | It does not assign fields itself in this example |
| Can it be customized? | Yes, with extra code in its body | It is written as an alternative entry point |
Canonical and additional constructors in the FullName class
Now compare the one-name route with the two-name route. A caller who supplies both a first and last name uses the canonical constructor directly, so no chaining happens at all. Both routes end at the same place, the canonical constructor, which means both produce objects that are set up the same way. The only difference is how the arguments reached it.
Customizing the Canonical Constructor
A canonical constructor can be extended with extra code. The customized form omits the parameter list. In the source's syntax, the definition is written as public FullName followed by a block of code, with no parentheses after the class name. This can look as though the parameters have disappeared, but they have not. A call to the constructor still requires two arguments, and those arguments still initialize firstName and lastName.
- There is no parameter list after FullName, but the components
firstNameandlastNameare still the inputs. - The if statement is the extra logic that runs in the constructor body.
- The throw statement stops construction when the check fails.
Order of Operations in a Canonical Constructor
Parameters initialize the fields before the constructor's own code is executed. This ordering explains why validation in the body works. The arguments are first used to fill the fields, and only then does the constructor's extra code run. So when the check tests whether firstName is null, the value it tests is the one the caller supplied.
The diagram reads as a sequence. The call supplies values, the values fill the fields, the body performs its extra work, and only then is the object handed back. The body is the only place where you can enforce rules about what the fields contain, and it runs after the fields have been filled.
Where a Validation Failure Happens
Consider two calls to the customized FullName record. In the first, new FullName(null, 'Lovelace') is called. The fields are initialized first, so firstName receives null. The check then tests firstName, finds it null, and the throw statement runs. The failure happens inside the constructor body, during the construction call itself. No object is returned to the caller.
In the second call, only the first argument changes, to new FullName('Ada', 'Lovelace'). The fields are initialized the same way, but the test is false, the throw statement is skipped, and the constructor completes normally. The same code path produces both outcomes, and the difference comes entirely from the value the caller passed in.
What do you think happens?
When new FullName(null, 'Lovelace') runs, does the caller ever receive a FullName object?
Reveal answer
No. The check fails inside the constructor body and the exception is thrown, so construction stops and no object is returned.
The fields were already filled with null before the check ran, but the object is never handed back to the caller because the throw statement interrupts construction at the call site.
Mistakes When Writing Alternative Constructors
Common mistake Assigning the fields directly in the one-parameter constructor as well as delegating
What it looks likeFullName(String onlyName) { firstName = onlyName; this(onlyName, null); }Why it failsThe
this(...)call must be the first statement in the constructor, and assigning fields in the one-parameter constructor duplicates work that belongs in the canonical constructor.Do this insteadKeep the one-parameter constructor to a single this(onlyName, null) call so that all field assignments happen in one place.
Common mistake Expecting the customized constructor to need no arguments because the parameter list is missing
What it looks likenew FullName() used with the customized record that has no parameter list
Why it failsDropping the parameter list does not remove the parameters; calls still supply the components.
Do this insteadPass one argument for each component, such as new FullName('Ada', 'Lovelace').
Common mistake Checking a field expecting the check to see a default value instead of the caller's argument
Why it failsParameters initialize the fields before the body runs, so the body sees the values that were passed in.
Do this insteadWrite checks against the field names, knowing they already hold the supplied values.
Common mistake Looking for the failure at the later point where the object is used, rather than at construction
Practice Tracing Constructor Calls
Using the customized FullName record with the null check, predict what happens for each of these calls: new FullName('Grace', 'Hopper'), new FullName(null, 'Turing'), and new FullName('Alan', null). For each, say which step is reached first, whether the check passes, and whether an object is created.
Hints
- The fields are filled from the arguments before the check runs.
- Only the value of
firstNameis tested by the check in this example. - A failed check stops construction at the call site, so no object is returned.
Key Takeaways
- An additional constructor is any constructor other than the canonical constructor, and it gives callers an alternative way to build an object.
- A one-parameter constructor can delegate to the canonical constructor with
this(...), so that the field assignments happen in one place. - A customized canonical constructor omits the parameter list but still receives the arguments, which initialize the fields before the constructor body runs.
- A validation check in the body sees the supplied values, and when it fails the exception is thrown during the construction call, so no object is returned.