Topic 4.3
Constructors and Constructor Chaining
In one line
A constructor is the special block that runs once when an object is created with new, so the object starts life complete and valid. Constructors can be overloaded and can call each other with this(...), keeping the setup logic in one place.
Think of it like this
A new phone's setup screen. Before you can use the phone, it asks for your language, Wi-Fi and account. You can't skip to the home screen with half the settings missing. Some phones offer "quick setup" that fills in sensible defaults and then runs the same full setup. A constructor is that setup screen, and "quick setup calls full setup" is constructor chaining.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Constructor
- A special block with the class's name and no return type. It runs once, when
newcreates an object, to set it up. - Default constructor
- The no-argument constructor the compiler writes for you when your class declares no constructor at all.
- No-arg constructor
- Any constructor with no parameters, whether you wrote it or the compiler did.
- Constructor overloading
- Having several constructors in one class, each with a different list of parameters.
- Constructor chaining
- One constructor calling another of the same class with
this(...), or the parent's withsuper(...). - Invariant
- A rule that must always be true for an object, for example "balance is never negative". Constructors set it up; methods keep it true.
- Instance initialiser block
- A bare
{ ... }block inside the class body. It runs for every new object, along with the field initialisers. - `<init>`
- The name constructors get in bytecode.
javapshows a constructor as"<init>".
Step by step
01The problem constructors solve
Without a constructor, creating a point takes three statements: Point p = new Point(); p.x = 3; p.y = 4;. Between the first and third line the point exists half-built. Forget a line and you have a point at (3, 0) and no error anywhere.
A constructor makes creation one step: new Point(3, 4). There's no moment where the object is visible but unfinished.
class Point {
int x;
int y;
Point(int x, int y) { // same name as the class, no return type
this.x = x; // this.x is the field, x is the parameter
this.y = y;
}
}
Point p = new Point(3, 4);02The default constructor, and how you lose it
In Topic 4.1 you wrote new Dog() without writing any constructor. The compiler had silently added Dog() { super(); }. javap Dog shows it.
Add Point(int x, int y) and the compiler stops generating the free one. Every new Point() in your code now fails. This is deliberate: once you say "a point needs coordinates", Java won't quietly allow coordinate-less points.
If you still want both, write the no-arg constructor yourself, ideally chaining to the main one.
03Overloaded constructors and this(...) chaining
A Pizza can be ordered three ways: with everything specified, with just a size (default cheese), or with nothing (medium, cheese). Instead of three copies of the setup, the short constructors delegate to the full one.
this(size, "cheese") means "run the other constructor of this class with these arguments". The chain ends in one canonical constructor that does the real work and validation. Change the rules once, and every path picks it up.
class Pizza {
String size;
String topping;
Pizza(String size, String topping) { // the one that does the work
if (!size.equals("S") && !size.equals("M") && !size.equals("L")) {
throw new IllegalArgumentException("bad size: " + size);
}
this.size = size;
this.topping = topping;
}
Pizza(String size) {
this(size, "cheese"); // must be the first statement
}
Pizza() {
this("M"); // chains twice
}
}04Why this(...) must come first (and what Java 25 changed)
The rule "this(...) or super(...) must be the first statement" guarantees the object is initialised exactly once, top-down, before your code uses it. If you print something and then call this(...), javac says call to this must be first statement in constructor.
A chain that loops (A() calls this(1), A(int) calls this()) would recurse forever, so the compiler rejects it: recursive constructor invocation.
Java 25 finalised flexible constructor bodies (JEP 513): statements that don't touch the object under construction, such as argument validation, may now appear *before* this(...) or super(...). This course's runnable examples target Java 17, so they keep the classic rule.
05The exact order of construction
For new Widget("w1") the JVM: (1) allocates and zeroes the object; (2) calls the constructor; the constructor first calls super() (here Object()); (3) runs field initialisers and instance initialiser blocks in the order they're written; (4) runs the remaining constructor body.
If the constructor starts with this(...), step 3 happens inside the constructor it chains to, not twice. The compiler copies field initialiser code into every constructor that calls super(...) and into none that call this(...). That's how "exactly once" is guaranteed.
06Validate in the constructor: no invalid objects
If a constructor throws an exception, new doesn't return a reference, so nothing can use the half-made object (it becomes garbage). That makes the constructor the perfect gatekeeper.
Standard choices: IllegalArgumentException for a bad value, NullPointerException via Objects.requireNonNull(x, "x") for a missing one. The message should say what was wrong and what value arrived.
Account(String owner, long opening) {
this.owner = java.util.Objects.requireNonNull(owner, "owner");
if (opening < 0) {
throw new IllegalArgumentException("opening balance < 0: " + opening);
}
this.balance = opening;
}07Copy constructors
A constructor that takes another object of the same class and copies its fields is called a copy constructor: Point(Point other) { this(other.x, other.y); }.
It's the clearest way to duplicate an object in Java, much simpler and safer than clone(). Collections use the same idea: new ArrayList<>(otherList) copies a list. Topic 4.11 uses copies to protect immutable objects.
Try it yourself
- 1
Add a fourth way to order
Add
Pizza(int slices)that picks "S" for up to 4 slices, "M" up to 8 and "L" above, and chains toPizza(String). You need to compute the size inside thethis(...)argument (hint: a ternary or astatichelper method). Predict whatnew Pizza(6).describe()prints. - 2
Move a field initialiser
In "Watch the construction order", move the
String b = ...line below theWidget()constructor. Predict whether the output changes. (Constructor position doesn't matter; field order relative to blocks does.) - 3
Lose the default constructor
In the copy constructor example, add
Point q = new Point();tomain. Read the error, then fix it by addingPoint() { this(0, 0); }.
Code & diagrams
Expected output
L pizza with paneer
S pizza with cheese
M pizza with cheese
rejected: bad size: XXLInitialisers ran once per object, even through the this(...) chain.
Expected output
-- new Widget("w1")
field a initialiser
instance initialiser block
field b initialiser
Widget(String) body, name = w1
-- new Widget()
field a initialiser
instance initialiser block
field b initialiser
Widget(String) body, name = default
Widget() body, after this(...)Expected output
p = 3,4
copy = 99,4
same object? false// What you write:
class Dog {
String name;
}
// What the compiler effectively produces:
class Dog {
String name;
Dog() { // same access as the class (here package-private)
super(); // calls Object()
}
}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Put void in front of a constructor
Write void Point(int x, int y) { ... } instead of Point(int x, int y) { ... }, then call new Point(3, 4).
Break #2
Chain constructors in a circle
Write Point() { this(0); } and Point(int x) { this(); }.
Myth vs fact
Myth
Every class always has a no-arg constructor.
Fact
Only if you write one or write no constructors at all. Declaring any constructor stops the compiler from generating the default.
Myth
A constructor creates the object.
Fact
new allocates and zeroes the object first; the constructor only initialises memory that already exists. That's why this is usable inside the constructor.
Myth
Constructors are inherited like methods.
Fact
Constructors are never inherited. A subclass must declare its own and chain to the parent's with super(...) (Topic 5.2).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
In bytecode every constructor is a method named
<init>returningvoid, invoked withinvokespecial. javac inlines the field initialisers and instance initialiser blocks into each<init>that callssuper(...)(and skips them in ones that start withthis(...)). That's how they run exactly once per object. - ▸
Calling an overridable method from a constructor is dangerous: in Phase 5 you'll see a subclass override run before the subclass's own fields are initialised, reading
nullor0. Effective Java Item 19: constructors must not invoke overridable methods. - ▸
Letting
thisescape during construction (registering a listener, starting a thread withthis, storing it in a static list) exposes a half-built object. Withfinalfields the Java Memory Model's freeze guarantee only holds ifthisdoesn't escape before the constructor ends (JLS 17.5). - ▸
When the parameter list grows past 3 or 4, overloaded constructors become hard to read ("telescoping constructors"). Use a static factory method (Topic 4.6) or the Builder pattern (System Design course, design patterns) instead.
Remember this
- 1
A constructor has the same name as the class and no return type (not even
void). It runs after the object's memory has been allocated and zeroed, and its job is to put the object into a valid starting state:new Point(3, 4)produces a point that already hasx = 3andy = 4. - 2
If you write no constructor at all, the compiler adds a default constructor: no parameters, same access as the class, and it only calls the parent's constructor. The moment you write any constructor yourself, the default one is no longer generated. That's why adding
Point(int x, int y)breaks every existingnew Point(). - 3
Constructors can be overloaded like methods (Topic 3.4): several constructors with different parameter lists. To avoid copying setup code into each one, one constructor calls another with
this(...). The rule (up to Java 24):this(...)orsuper(...)must be the first statement of the constructor, and you can't have both. - 4
The full creation order for
newis: allocate and zero memory; call the superclass constructor (super(...), implicit if you don't write it); run instance field initialisers and instance initialiser blocks in source order; then run the rest of your constructor body. Topic 5.2 adds the inheritance part. - 5
A constructor is the right place to validate arguments. If
new Account(-100)makes no sense, throwIllegalArgumentExceptionfrom the constructor, and no invalid object ever escapes. This idea, "an object is valid from the moment it exists", is the basis of encapsulation (Topic 4.5) and immutability (Topic 4.11).
Explain it without notes
What is a constructor, and how is it different from a method?
When does the compiler generate a default constructor, and when doesn't it?
Describe the full order of events in new Widget("w1") for a class with field initialisers and an instance initialiser block.
Why must this(...) be the first statement of a constructor, and what changed in Java 25?
Why is a constructor a good place to validate input?
Practice
Write a class Temperature with a double celsius field and two constructors: Temperature(double celsius) and Temperature() which chains to 20.0. Reject values below -273.15 with IllegalArgumentException. Print two temperatures and a rejected one.
Write a class Rect with constructors Rect(int w, int h) and Rect(int side) (a square, chaining to the first) and a method area(). Print the area of a 3x5 rectangle and a 4x4 square.
Write a class Counter with an instance initialiser block that prints init block, a field initialiser int start = 10; and a constructor that prints constructor, start = <start>. Create two counters and show the output.
Trade-offs
- ↔
Constructors guarantee a valid object, but overloads with several parameters of the same type (
new Rect(3, 5): which is width?) are easy to call wrongly. For more than a few parameters, prefer named static factories or a builder. - ↔
Doing heavy work in a constructor (network calls, file reads) makes objects slow and hard to test and can leave resources open if it throws. Keep constructors to assigning and validating; do I/O in a factory method or a separate
start()/open()step.
Done when you can
Done when you can write constructors with and without parameters and explain why the default disappears.
Done when you can chain constructors with
this(...)to a single canonical one.Done when you can list the exact order of construction steps.
Done when you validate arguments in constructors so invalid objects never exist.
Done when you can write a copy constructor and explain why it beats copying fields by hand.