Command Palette

Search for a command to run...

PHASE 4Beginner ~29 min· topic 3 of 12

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 new creates 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 with super(...).
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. javap shows 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.

Main.javawhole filejava
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.

terminal
$ javac Main.java
── expected output ──
Main.java:3: error: constructor Point in class Point cannot be applied to given types;
Point p = new Point();
^
required: int,int
found: no arguments
reason: actual and formal argument lists differ in length
1 error

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.

Main.javawhole filejava
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
    }
}
Overloaded constructors and this(...) chainingdiagram
Rendering diagram…

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.

terminal
$ javac Main.java
── expected output ──
Main.java:1: error: call to this must be first statement in constructor
class Point { int x, y; Point() { System.out.println("hi"); this(0, 0); } ... }
^
1 error

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.

The exact order of constructiondiagram
Rendering diagram…

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.

Main.javawhole filejava
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. 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 to Pizza(String). You need to compute the size inside the this(...) argument (hint: a ternary or a static helper method). Predict what new Pizza(6).describe() prints.

  2. 2

    Move a field initialiser

    In "Watch the construction order", move the String b = ... line below the Widget() constructor. Predict whether the output changes. (Constructor position doesn't matter; field order relative to blocks does.)

  3. 3

    Lose the default constructor

    In the copy constructor example, add Point q = new Point(); to main. Read the error, then fix it by adding Point() { this(0, 0); }.

Code & diagrams

Overloaded constructors chaining to one New tab
Sign in to run this example in your browser.

Expected output

L pizza with paneer
S pizza with cheese
M pizza with cheese
rejected: bad size: XXL
Watch the construction order New tab

Initialisers ran once per object, even through the this(...) chain.

Sign in to run this example in your browser.

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(...)
Copy constructor New tab
Sign in to run this example in your browser.

Expected output

p    = 3,4
copy = 99,4
same object? false
The default constructor javac generatesjava
// 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).

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: constructor Point in class Point cannot be applied to given types;
Point p = new Point(3, 4);
^
required: no arguments
found: int,int
reason: actual and formal argument lists differ in length
1 error

Break #2

Chain constructors in a circle

Write Point() { this(0); } and Point(int x) { this(); }.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: recursive constructor invocation
Point(int x) { this(); }
^
1 error

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> returning void, invoked with invokespecial. javac inlines the field initialisers and instance initialiser blocks into each <init> that calls super(...) (and skips them in ones that start with this(...)). 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 null or 0. Effective Java Item 19: constructors must not invoke overridable methods.

  • ▸

    Letting this escape during construction (registering a listener, starting a thread with this, storing it in a static list) exposes a half-built object. With final fields the Java Memory Model's freeze guarantee only holds if this doesn'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. 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 has x = 3 and y = 4.

  2. 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 existing new Point().

  3. 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(...) or super(...) must be the first statement of the constructor, and you can't have both.

  4. 4

    The full creation order for new is: 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. 5

    A constructor is the right place to validate arguments. If new Account(-100) makes no sense, throw IllegalArgumentException from 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

01

What is a constructor, and how is it different from a method?

02

When does the compiler generate a default constructor, and when doesn't it?

03

Describe the full order of events in new Widget("w1") for a class with field initialisers and an instance initialiser block.

04

Why must this(...) be the first statement of a constructor, and what changed in Java 25?

05

Why is a constructor a good place to validate input?

Practice

01

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.

02

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.

03

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.