Skip to content
elephantoo

Constructors & this

Lesson 11 of 43 14 min read

Default and parameterised constructors, overloading, this(...) chaining and initialisation order.


In the previous lesson we created objects and then filled in their fields one by one. That is clumsy, and it is easy to forget a field and leave an object half-built. A constructor is special code that runs when an object is created, so every object starts life in a valid state.

Writing a constructor#

A constructor looks like a method, but it has the same name as the class and no return type (not even void):

Student.java
public class Student {
    String name;
    int grade;

    Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }

    public static void main(String[] args) {
        Student s = new Student("Kiran", 9);
        System.out.println(s.name + " is in grade " + s.grade);
    }
}
Output
Kiran is in grade 9

new Student("Kiran", 9) does three things:

  1. Allocates memory for a new Student object, with fields set to default values.
  2. Runs the constructor with the arguments you passed.
  3. Returns a reference to the finished object.

The this keyword#

Inside a constructor or instance method, this is a reference to the current object. It is needed when a parameter has the same name as a field:

Java
Student(String name, int grade) {
    this.name = name;    // field = parameter
    this.grade = grade;
}

Without this., the name name refers to the parameter, which shadows the field. Writing name = name; just assigns the parameter to itself, and the field stays null. The compiler will not stop you, so this bug is surprisingly common.

this can also be passed as an argument (register(this)) or returned from a method (useful for chaining calls).

The default constructor#

If you write no constructor at all, Java quietly adds a no-argument constructor that does nothing. That is why new Book() worked in the last lesson.

As soon as you write any constructor, that free default disappears:

Java
class Person {
    String name;
    Person(String name) { this.name = name; }
}

Person p = new Person();   // compile error: constructor Person in class Person cannot be applied to given types

If you still want a no-arg constructor, declare it yourself.

Overloaded constructors#

Like methods, constructors can be overloaded with different parameter lists, giving callers several convenient ways to create an object:

Pizza.java
public class Pizza {
    String size;
    String topping;
    boolean extraCheese;

    Pizza() {
        this("medium", "margherita", false);
    }

    Pizza(String size) {
        this(size, "margherita", false);
    }

    Pizza(String size, String topping, boolean extraCheese) {
        this.size = size;
        this.topping = topping;
        this.extraCheese = extraCheese;
    }

    @Override
    public String toString() {
        return size + " " + topping + (extraCheese ? " + extra cheese" : "");
    }

    public static void main(String[] args) {
        System.out.println(new Pizza());
        System.out.println(new Pizza("large"));
        System.out.println(new Pizza("small", "paneer tikka", true));
    }
}
Output
medium margherita
large margherita
small paneer tikka + extra cheese

Constructor chaining with this(...)#

Notice how the shorter constructors above call the full one with this(...). This is constructor chaining: all the real initialisation lives in one place, and the other constructors just supply defaults. The rules:

  • this(...) must be the first statement in the constructor.
  • A constructor can call either this(...) or super(...) (the parent's constructor, covered in the inheritance lesson), not both.
  • Chains cannot be circular.

Validating in the constructor#

A constructor is the perfect place to reject bad data, so an invalid object can never exist:

Temperature.java
public class Temperature {
    final double celsius;

    Temperature(double celsius) {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("Below absolute zero: " + celsius);
        }
        this.celsius = celsius;
    }

    double fahrenheit() {
        return celsius * 9 / 5 + 32;
    }

    public static void main(String[] args) {
        Temperature t = new Temperature(37);
        System.out.println(t.fahrenheit());
        try {
            new Temperature(-300);
        } catch (IllegalArgumentException e) {
            System.out.println("Rejected: " + e.getMessage());
        }
    }
}
Output
98.6
Rejected: Below absolute zero: -300.0

throw new IllegalArgumentException(...) stops construction with an error; try/catch handles it. Both are explained fully in the exceptions lesson.

final fields#

A field marked final must be assigned exactly once, either where it is declared or in every constructor, and can never change afterwards:

Java
class Order {
    final String id;
    final long createdAt = System.currentTimeMillis();

    Order(String id) {
        this.id = id;        // required: forgetting this is a compile error
    }
}

Making fields final whenever possible makes objects easier to reason about. Objects whose fields are all final (and hold immutable values) are immutable, like String.

Initialisation order#

When you call new, things happen in a well-defined order:

InitOrder.java
public class InitOrder {
    String a = log("1. field initialiser");

    {
        log("2. instance initialiser block");
    }

    InitOrder() {
        log("3. constructor body");
    }

    static String log(String msg) {
        System.out.println(msg);
        return msg;
    }

    public static void main(String[] args) {
        new InitOrder();
    }
}
Output
1. field initialiser
2. instance initialiser block
3. constructor body
  1. Fields get default values (0, false, null).
  2. Field initialisers and instance initialiser blocks { ... } run, top to bottom.
  3. The constructor body runs.

(If a parent class is involved, the parent's part is initialised first.) Instance initialiser blocks are rare in practice. Prefer putting logic in constructors.

Copy constructors#

A constructor that takes another object of the same class creates an independent copy:

Copying.java
import java.util.Arrays;

public class Copying {
    public static void main(String[] args) {
        Playlist original = new Playlist("Road trip", new String[]{"Song A", "Song B"});
        Playlist copy = new Playlist(original);
        copy.songs[0] = "Song Z";
        System.out.println(Arrays.toString(original.songs));
        System.out.println(Arrays.toString(copy.songs));
    }
}

class Playlist {
    String name;
    String[] songs;

    Playlist(String name, String[] songs) {
        this.name = name;
        this.songs = songs;
    }

    Playlist(Playlist other) {
        this.name = other.name;
        this.songs = other.songs.clone();   // deep enough: copy the array too
    }
}
Output
[Song A, Song B]
[Song Z, Song B]

If the copy constructor had written this.songs = other.songs;, both playlists would share one array, and changing one would change the other.

Too many parameters? The builder idea#

A constructor with seven parameters, such as new User("a", "b", null, true, false, 3, null), is hard to read. Common solutions:

  • Provide a few overloaded constructors for common cases.
  • Use a builder object with named setter-style methods:
BuilderDemo.java
public class BuilderDemo {
    public static void main(String[] args) {
        Email mail = new Email.Builder("team@elephantoo.com")
                .subject("Welcome!")
                .body("Thanks for joining.")
                .build();
        System.out.println(mail);
    }
}

class Email {
    private final String to, subject, body;

    private Email(Builder b) {
        this.to = b.to;
        this.subject = b.subject;
        this.body = b.body;
    }

    @Override
    public String toString() {
        return "To: " + to + " | " + subject + " | " + body;
    }

    static class Builder {
        private final String to;
        private String subject = "(no subject)";
        private String body = "";

        Builder(String to) { this.to = to; }
        Builder subject(String s) { this.subject = s; return this; }
        Builder body(String b) { this.body = b; return this; }
        Email build() { return new Email(this); }
    }
}
Output
To: team@elephantoo.com | Welcome! | Thanks for joining.

Each builder method returns this, which enables the fluent chain. You will see builders everywhere in Java libraries (HttpRequest.newBuilder(), StringBuilder, Lombok's @Builder). It uses private and a static nested class, which are covered in the next two lessons.

Common mistakes#

  • Adding a return type: void Student(...) is a method named Student, not a constructor, so your fields never get set.
  • Forgetting this. when parameter and field share a name.
  • Expecting the default constructor after declaring another constructor.
  • Calling this(...) anywhere other than the first line.
  • Doing heavy work (network calls, file reads) in constructors. Keep them focused on initialisation.

What's next#

Constructors set up each object's own data. But some data belongs to the class as a whole, such as a counter of how many objects exist. That is the job of the static keyword, up next.

Check your understanding

Quick quiz

0/3 answered
  1. 1.A class declares only Person(String name) { ... }. What does new Person() do?

  2. 2.Where must a this(...) call appear inside a constructor?

  3. 3.Inside a constructor with parameter name, what does name = name; do?

Finished reading?

Mark this lesson complete to track your progress.