Skip to content
Laviyx Learn

Try

Java course menu

Java Constructors: Default, Parameterized, this() and Initialization Order

Learn Java constructors: the default constructor, constructors with parameters, this.field = field, overloading constructors and calling this(...), validating arguments, final fields, copy constructors, and the order in which objects are initialized.

Intermediate13 min readUpdated
On this page

A constructor is a special method that runs when an object is created with new. Its job is to put the new object into a valid starting state, so it never exists half-initialized.

A constructor with parameters#

A constructor has the same name as the class and no return type:

Java
class Student {
    String name;
    int year;

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

    void print() {
        System.out.println(name + " (year " + year + ")");
    }
}

public class Main {
    public static void main(String[] args) {
        Student a = new Student("Asha", 2);
        Student b = new Student("Ravi", 3);
        a.print();
        b.print();
    }
}
Output
Asha (year 2)
Ravi (year 3)

The parameters have the same names as the fields, so this.name means "the field" and plain name means "the parameter". Because the only constructor requires a name and a year, it is impossible to create a Student without them.

The default constructor#

If a class declares no constructors, Java provides a no-argument default constructor that leaves fields at their default values. As soon as you write any constructor, the default one is no longer generated:

Java
class Student {
    String name;

    Student(String name) {
        this.name = name;
    }
}

public class Main {
    public static void main(String[] args) {
        Student s = new Student();   // error: constructor Student() not found
    }
}

If you still need a no-argument constructor, write it explicitly.

Overloaded constructors and this(...)#

A class can have several constructors with different parameters. One constructor can call another with this(...), which must be the first statement, so the setup logic lives in one place:

Java
class Timer {
    private final int totalSeconds;

    Timer(int minutes, int seconds) {
        this.totalSeconds = minutes * 60 + seconds;
    }

    Timer(int seconds) {
        this(0, seconds);   // delegate to the two-argument constructor
    }

    Timer() {
        this(0);
    }

    @Override
    public String toString() {
        return totalSeconds / 60 + "m " + totalSeconds % 60 + "s";
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Timer(2, 30));
        System.out.println(new Timer(95));
        System.out.println(new Timer());
    }
}
Output
2m 30s
1m 35s
0m 0s

Validating arguments#

A constructor is the natural place to reject invalid data, by throwing an exception before the object exists:

Java
class Product {
    private final String name;
    private final double price;

    Product(String name, double price) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("name is required");
        }
        if (price < 0) {
            throw new IllegalArgumentException("price cannot be negative");
        }
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return name + " @ " + price;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Product("Pen", 25));
        try {
            new Product("Bag", -10);
        } catch (IllegalArgumentException e) {
            System.out.println("Rejected: " + e.getMessage());
        }
    }
}
Output
Pen @ 25.0
Rejected: price cannot be negative

final fields#

A final field must be assigned exactly once, normally in the constructor, and can never change afterwards. Making fields final whenever possible produces objects that are simpler and safer to share. The compiler reports an error if any constructor forgets to assign a final field.

Copy constructors#

Java has no built-in copy constructor, but a common pattern is a constructor that takes another object of the same class:

Java
import java.util.ArrayList;
import java.util.List;

class Playlist {
    private final String name;
    private final List<String> songs;

    Playlist(String name) {
        this.name = name;
        this.songs = new ArrayList<>();
    }

    Playlist(Playlist other) {                  // copy constructor
        this.name = other.name;
        this.songs = new ArrayList<>(other.songs);   // copy the list, not just the reference
    }

    void add(String song) { songs.add(song); }
    int size() { return songs.size(); }
}

public class Main {
    public static void main(String[] args) {
        Playlist original = new Playlist("Road trip");
        original.add("Song A");

        Playlist copy = new Playlist(original);
        copy.add("Song B");

        System.out.println(original.size() + " " + copy.size());
    }
}
Output
1 2

Copying the list itself (a deep copy) keeps the two playlists independent. Copying only the reference (this.songs = other.songs) would make both share one list.

Initialization order#

When new runs, Java:

  1. sets every field to its default value;
  2. runs field initializers (int count = 5;) and instance initializer blocks, top to bottom;
  3. runs the constructor body.

(With inheritance, the parent class is initialized first; see Inheritance.)

Java
class Order {
    private int items = 1;               // step 2
    private String status = "new";

    Order(int items) {
        System.out.println("before: " + this.items + ", " + status);
        this.items = items;              // step 3
        System.out.println("after: " + this.items);
    }
}

public class Main {
    public static void main(String[] args) {
        new Order(5);
    }
}
Output
before: 1, new
after: 5

Common mistakes#

Practice#

Practice exercise

Write a class Fraction with final fields numerator and denominator. The constructor should throw IllegalArgumentException if the denominator is 0, and store the fraction in lowest terms. Add a one-argument constructor for whole numbers. Print new Fraction(6, 8) and new Fraction(5).

Show solution
Java
class Fraction {
    private final int numerator;
    private final int denominator;

    Fraction(int numerator, int denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException("denominator is zero");
        }
        int g = gcd(Math.abs(numerator), Math.abs(denominator));
        this.numerator = numerator / g;
        this.denominator = denominator / g;
    }

    Fraction(int whole) {
        this(whole, 1);
    }

    private static int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }

    @Override
    public String toString() {
        return numerator + "/" + denominator;
    }
}

public class Fractions {
    public static void main(String[] args) {
        System.out.println(new Fraction(6, 8));
        System.out.println(new Fraction(5));
    }
}
Output
3/4
5/1

Key takeaway#

Finished this lesson?