Module 5 · Object-Oriented Fundamentals ⏱ 18 min

Constructors and Initialization Order

By the end of this lesson you will be able to:
  • Write overloaded constructors and chain them with this()
  • Predict field values after initialization blocks and constructors run
  • Explain why the default constructor disappears when you add a parameterized one

Suppose you build a BankAccount without a constructor. Every new account starts with balance = 0, which is fine, but owner starts as null. The first caller who tries to use that account gets a NullPointerException because the object was born incomplete. That is the problem constructors solve: they guarantee every object begins life in a valid, known state. Without a constructor, you are asking every caller to remember to initialise every field, and in a large program someone will forget. A constructor is not optional decoration — it is the contract between the class and the rest of the program that says, if you want one of these, you must provide what it needs to be whole. That contract is enforced by the compiler: miss a required argument and the build fails before the program ever runs.

flowchart LR
  A["new BankAccount(#quot;Mara#quot;, 100)"] --> B["fields set to defaults<br/>owner=null, balance=0"]
  B --> C["instance initializers run"]
  C --> D["constructor runs<br/>owner=#quot;Mara#quot;, balance=100"]
  D --> E["object ready"]
  style D fill:#c2410c,color:#fff
A constructor turns raw memory into a valid object by setting fields the caller supplied.

The anatomy of a constructor

A constructor looks like a method but has two distinguishing rules: its name is exactly the class name, and it has no return type — not even void. You cannot accidentally type int Person() or void Person(); the compiler will reject both. When you write new BankAccount("Mara", 100), Java allocates memory, sets fields to default values (0 for numbers, false for booleans, null for objects), and then runs the constructor you wrote. If you do not write any constructor, Java quietly inserts a default constructor that takes no arguments and does nothing. That is why new BankAccount() sometimes compiles and sometimes does not — the moment you add a constructor with parameters, the default one vanishes. We will come back to that trap, because it bites every Java developer at least once.

Main.java — a constructor ensures every Person starts with a name and age. Real, compiling Java.
public class Main {
    static class Person {
        String name;
        int age;

        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        void greet() {
            System.out.println("Hi, I am " + name);
        }
    }

    public static void main(String[] args) {
        Person p = new Person("Jules", 30);
        p.greet();
    }
}

Overloading and chaining

A class can have many constructors as long as their parameter lists differ — this is constructor overloading. The no-argument constructor might supply sensible defaults, while the full-argument constructor accepts every value explicitly. To avoid repeating logic, one constructor can call another with this(...). The call must be the very first line of the constructor, and it routes to the constructor that matches the argument types. You cannot call this(...) after any other statement because the object must be fully initialised before you do anything else. This is how you centralise validation: put the checks in one constructor, and have every other constructor funnel through it. If the canonical constructor rejects a negative age, every shortcut constructor rejects it too.

flowchart TD
  A["Person()"] -->|"this(#quot;Unknown#quot;, 0)"| B["Person(String, int)"]
  C["Person(String name)"] -->|"this(name, 0)"| B
  B --> D["validation runs<br/>fields set"]
  style B fill:#c2410c,color:#fff
Multiple constructors can delegate to one canonical constructor using this(...).
Main.java — three constructors, all chaining to one. Real, compiling Java.
public class Main {
    static class Counter {
        int value;
        String label;

        Counter() {
            this(0, "default");
        }

        Counter(int value) {
            this(value, "count");
        }

        Counter(int value, String label) {
            this.value = value;
            this.label = label;
        }

        void print() {
            System.out.println(label + ": " + value);
        }
    }

    public static void main(String[] args) {
        Counter a = new Counter();
        Counter b = new Counter(5);
        a.print();
        b.print();
    }
}

Initialization order

When an object is created, Java follows a strict sequence. First, fields are set to default values (0, false, null). Second, any instance initializer blocks — anonymous braces at class level — run in the order they appear. Third, the constructor runs. This order is reliable, so you can reason about it, but it also means a field assignment written inline (int value = 10;) is effectively moved into the instance-initializer stage. If an initializer block and the constructor both touch the same field, the constructor wins because it runs last. You can use this deliberately — set a safe default inline, then override it in the constructor with a caller-supplied value — but mixing too many initialisation styles in one class makes the code hard to follow.

flowchart TD
  A["1. Fields to defaults"] --> B["2. Instance initializers"]
  B --> C["3. Constructor body"]
  C --> D["Object ready"]
  style C fill:#c2410c,color:#fff
Java always initializes in this order: defaults, then instance initializers, then the constructor.
Main.java — fields, an instance initializer, and a constructor all touch the same variable. Real, compiling Java.
public class Main {
    static class Demo {
        int a = 1;
        int b;

        {
            b = 2;
        }

        Demo() {
            a = 3;
        }
    }

    public static void main(String[] args) {
        Demo d = new Demo();
        System.out.println("a=" + d.a + ", b=" + d.b);
    }
}
Exercise

What does this program print? Read it carefully and type the exact output.

public class Main {
    static class Point {
        int x;
        int y;

        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    public static void main(String[] args) {
        Point p = new Point(3, 4);
        System.out.println(p.x + p.y);
    }
}
Exercise

Initialization order check. Predict the exact two-line output.

public class Main {
    static class Demo {
        int a = 1;
        int b;

        {
            b = 2;
        }

        Demo() {
            a = 3;
        }
    }

    public static void main(String[] args) {
        Demo d = new Demo();
        System.out.println(d.a);
        System.out.println(d.b);
    }
}
Exercise

You define class Person { Person(String name) {} } and then write new Person(). What happens?

Exercise

Constructor chaining. Predict the exact one-line output.

public class Main {
    static class Message {
        String text;

        Message() {
            this("Hello");
        }

        Message(String text) {
            this.text = text;
        }

        void show() {
            System.out.println(text);
        }
    }

    public static void main(String[] args) {
        Message m = new Message();
        m.show();
    }
}
Exercise

You need a Rectangle class that can be created as new Rectangle(), new Rectangle(5), or new Rectangle(5, 10). Which constructor design satisfies this?

Recap

  • A constructor has the same name as the class and no return type. It runs automatically when new creates an object.
  • If you write no constructor, Java supplies a default no-arg constructor. The moment you write any constructor, that default disappears.
  • You can overload constructors and chain them with this(...), but the call must be the first statement in the body.
  • Java initializes objects in a fixed order: fields to defaults, then inline field assignments and instance initializer blocks, then the constructor.
  • Because the constructor runs last, it can override values set earlier.

Next you will learn how to control what outside code can see and change, using access modifiers and encapsulation.

Checkpoint quiz

What is the first statement in a constructor that calls another constructor with this(...)?

In what order does Java initialise a new object?

Go deeper — technical resources