Constructors

IB Syllabus: B3.1: Design and implement constructors to initialise objects.


Key Concepts

What is a Constructor?

A constructor is a special method that is called automatically when a new object is created with the new keyword. Its purpose is to initialise the object’s attributes: setting them to their starting values so the object is in a valid, usable state from the moment it exists.

Three rules distinguish constructors from regular methods:

  1. The constructor name must match the class name exactly
  2. Constructors have no return type, not even void
  3. Constructors are called only once per object, at the moment of creation
public class Pet {
    private String name;
    private String species;
    private int age;

    // Constructor -- same name as the class, no return type
    public Pet(String name, String species, int age) {
        this.name = name;
        this.species = species;
        this.age = age;
    }
}
class Pet():
    # In Python the constructor is ALWAYS named __init__ -
    # it never matches the class name like Java's does
    def __init__(self, name, species, age):
        self._name = name
        self._species = species
        self._age = age
// The constructor runs automatically when new is called
Pet dog = new Pet("Max", "Dog", 3);
// At this point, dog.name is "Max", dog.species is "Dog", dog.age is 3
# The constructor runs automatically when the class is called - no "new" keyword
dog = Pet("Max", "Dog", 3)
# At this point, dog's name is "Max", species is "Dog", age is 3

A constructor creates an instance of a class and assigns values to its instance variables. This is one of the most precise definitions IB expects: memorise it.

Default Constructor

A default constructor takes no parameters and assigns default values to all attributes. If you do not write any constructor at all, Java provides an invisible default constructor that sets attributes to their zero-values (0 for numbers, null for objects, false for booleans).

However, once you write any constructor, Java stops providing the invisible one. If you want a no-argument option alongside a parameterised constructor, you must write both explicitly.

public class Book {
    private String title;
    private String author;
    private int pages;

    // Default constructor -- no parameters
    public Book() {
        this.title = "Untitled";
        this.author = "Unknown";
        this.pages = 0;
    }

    // Parameterised constructor
    public Book(String title, String author, int pages) {
        this.title = title;
        this.author = author;
        this.pages = pages;
    }

    public String getTitle() { return title; }
    public String getAuthor() { return author; }
    public int getPages() { return pages; }
}
class Book():
    # Python cannot overload the constructor - a class has exactly ONE __init__.
    # Default parameter values give the same effect as Java's
    # default + parameterised constructor pair.
    def __init__(self, title="Untitled", author="Unknown", pages=0):
        self._title = title
        self._author = author
        self._pages = pages

    def get_title(self):
        return self._title

    def get_author(self):
        return self._author

    def get_pages(self):
        return self._pages
Book b1 = new Book();                              // uses default constructor
Book b2 = new Book("Dune", "Frank Herbert", 412);  // uses parameterised constructor

System.out.println(b1.getTitle());  // Untitled
System.out.println(b2.getTitle());  // Dune
b1 = Book()                               # no arguments - the default values are used
b2 = Book("Dune", "Frank Herbert", 412)   # arguments replace the defaults

print(b1.get_title())   # Untitled
print(b2.get_title())   # Dune

If you define a parameterised constructor but forget to define a default constructor, code that calls new Book() will fail to compile. Java only provides the automatic default constructor when you write NO constructors at all.

Parameterised Constructor

A parameterised constructor accepts arguments that are used to set the object’s initial state. This is the most common type: it lets you create objects with specific values from the start.

public class Student {
    private String name;
    private int grade;

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

    public String getName() { return name; }
    public int getGrade() { return grade; }
}
class Student():
    def __init__(self, name, grade):
        self._name = name
        self._grade = grade

    def get_name(self):
        return self._name

    def get_grade(self):
        return self._grade
Student s = new Student("Maya", 11);
System.out.println(s.getName());   // Maya
System.out.println(s.getGrade());  // 11
s = Student("Maya", 11)
print(s.get_name())    # Maya
print(s.get_grade())   # 11

Overloaded Constructors

Constructor overloading means defining multiple constructors in the same class, each with a different parameter list. Java decides which constructor to call based on the arguments you provide.

public class Rectangle {
    private double width;
    private double height;

    // Constructor 1: both dimensions specified
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    // Constructor 2: square (equal sides)
    public Rectangle(double side) {
        this.width = side;
        this.height = side;
    }

    // Constructor 3: default 1x1
    public Rectangle() {
        this.width = 1.0;
        this.height = 1.0;
    }

    public double getArea() {
        return width * height;
    }
}
class Rectangle():
    # Python cannot overload __init__ - one constructor must cover all
    # three cases. Default values + a None check reproduce Java's trio.
    def __init__(self, width=1.0, height=None):
        if height == None:
            height = width       # one argument -> square; no arguments -> 1.0 x 1.0
        self._width = width
        self._height = height

    def get_area(self):
        return self._width * self._height
Rectangle r1 = new Rectangle(5.0, 3.0);  // calls constructor 1
Rectangle r2 = new Rectangle(4.0);       // calls constructor 2 (square)
Rectangle r3 = new Rectangle();          // calls constructor 3 (default)

System.out.println(r1.getArea());  // 15.0
System.out.println(r2.getArea());  // 16.0
System.out.println(r3.getArea());  // 1.0
r1 = Rectangle(5.0, 3.0)   # both dimensions given
r2 = Rectangle(4.0)        # one argument - a square
r3 = Rectangle()           # no arguments - the 1.0 x 1.0 default

print(r1.get_area())   # 15.0
print(r2.get_area())   # 16.0
print(r3.get_area())   # 1.0

Java matches the call to the constructor whose parameter list matches the arguments provided. If no match is found, the code will not compile. (Python has no such matching step: there is only one __init__, so it must handle every case itself.)

Constructor Validation

Constructors can include validation to prevent objects from being created with invalid data:

public class Temperature {
    private double celsius;

    public Temperature(double celsius) {
        if (celsius < -273.15) {
            this.celsius = -273.15;  // clamp to absolute zero
        } else {
            this.celsius = celsius;
        }
    }

    public double getCelsius() { return celsius; }
}
class Temperature():
    def __init__(self, celsius):
        if celsius < -273.15:
            self._celsius = -273.15   # clamp to absolute zero
        else:
            self._celsius = celsius

    def get_celsius(self):
        return self._celsius
Temperature t = new Temperature(-500.0);
System.out.println(t.getCelsius());  // -273.15 (clamped, not -500)
t = Temperature(-500.0)
print(t.get_celsius())   # -273.15 (clamped, not -500)

This ensures every Temperature object starts in a valid state, no object can exist with a temperature below absolute zero.

What Constructors Should NOT Do

Constructors should initialise attributes and perform basic validation. They should not:

  • Perform complex calculations or I/O operations
  • Call methods that depend on the object being fully initialised (the object is still being built)
  • Print output (unless for debugging): constructors should be silent

Worked Examples

Example 1: Choosing the Right Constructor

Scenario: A BankAccount class needs to support three ways of creating accounts:

  1. With owner name and initial balance
  2. With owner name only (balance defaults to 0)
  3. With no arguments (for temporary/test accounts)
public class BankAccount {
    private String owner;
    private double balance;

    public BankAccount(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public BankAccount(String owner) {
        this.owner = owner;
        this.balance = 0.0;
    }

    public BankAccount() {
        this.owner = "Temporary";
        this.balance = 0.0;
    }

    public String getOwner() { return owner; }
    public double getBalance() { return balance; }
}
class BankAccount():
    # One __init__ covers all three Java constructors:
    # defaults fill in whatever the caller leaves out
    def __init__(self, owner="Temporary", balance=0.0):
        self._owner = owner
        self._balance = balance

    def get_owner(self):
        return self._owner

    def get_balance(self):
        return self._balance
BankAccount a1 = new BankAccount("Alice", 1000.0);
BankAccount a2 = new BankAccount("Bob");
BankAccount a3 = new BankAccount();

System.out.println(a1.getOwner() + ": " + a1.getBalance());
System.out.println(a2.getOwner() + ": " + a2.getBalance());
System.out.println(a3.getOwner() + ": " + a3.getBalance());
a1 = BankAccount("Alice", 1000.0)
a2 = BankAccount("Bob")
a3 = BankAccount()

print(a1.get_owner() + ": " + str(a1.get_balance()))
print(a2.get_owner() + ": " + str(a2.get_balance()))
print(a3.get_owner() + ": " + str(a3.get_balance()))

Output:

Alice: 1000.0
Bob: 0.0
Temporary: 0.0

Example 2: Constructor with Validation

Scenario: A Grade class stores a percentage (0-100). The constructor must reject invalid values.

public class Grade {
    private int percentage;

    public Grade(int percentage) {
        if (percentage < 0) {
            this.percentage = 0;
        } else if (percentage > 100) {
            this.percentage = 100;
        } else {
            this.percentage = percentage;
        }
    }

    public int getPercentage() { return percentage; }

    public String getLetterGrade() {
        if (percentage >= 90) return "A";
        if (percentage >= 80) return "B";
        if (percentage >= 70) return "C";
        if (percentage >= 60) return "D";
        return "F";
    }
}
class Grade():
    def __init__(self, percentage):
        if percentage < 0:
            self._percentage = 0
        elif percentage > 100:
            self._percentage = 100
        else:
            self._percentage = percentage

    def get_percentage(self):
        return self._percentage

    def get_letter_grade(self):
        if self._percentage >= 90:
            return "A"
        elif self._percentage >= 80:
            return "B"
        elif self._percentage >= 70:
            return "C"
        elif self._percentage >= 60:
            return "D"
        else:
            return "F"
Grade g1 = new Grade(95);
Grade g2 = new Grade(-10);
Grade g3 = new Grade(150);

System.out.println(g1.getPercentage() + " = " + g1.getLetterGrade());
System.out.println(g2.getPercentage() + " = " + g2.getLetterGrade());
System.out.println(g3.getPercentage() + " = " + g3.getLetterGrade());
g1 = Grade(95)
g2 = Grade(-10)
g3 = Grade(150)

print(g1.get_percentage(), "=", g1.get_letter_grade())
print(g2.get_percentage(), "=", g2.get_letter_grade())
print(g3.get_percentage(), "=", g3.get_letter_grade())

Output:

95 = A
0 = F
100 = A

Quick Check

Q1. What is the primary purpose of a constructor?

Q2. What happens if you define a parameterised constructor but not a default constructor?

Q3. What is constructor overloading?

Q4. Which of the following is NOT a rule for constructors?

Q5. Given the BankAccount class from Example 1, what does new BankAccount("Bob") set balance to?


Trace Exercise

Trace the creation of three Rectangle objects and determine their areas.

Trace: Overloaded Constructors

Rectangle r1 = new Rectangle(6.0, 4.0);
Rectangle r2 = new Rectangle(5.0);
Rectangle r3 = new Rectangle();
ObjectwidthheightgetArea()
r1
r2
r3

Spot the Error

This class compiles, but new Player("Alice", 100) sets name to null and score to 0. Click the buggy line, then pick the fix.

1public class Player { 2 private String name; 3 private int score; 4 public void Player(String name, int score) { 5 this.name = name; 6 this.score = score; 7 } 8}

Pick the fix:


Predict the Output

Using the Grade class from Example 2:

Grade g = new Grade(-10);
System.out.println(g.getLetterGrade());

What is printed?

What does this print?

Grade g = new Grade(150);
System.out.println(g.getPercentage() + " " + g.getLetterGrade());

What is printed?


Practice Exercises

Core

  1. Write a constructor: Create a Song class with attributes title (String), artist (String), and durationSeconds (int). Write a parameterised constructor and getters for all three. Create two Song objects in main and print their details.

  2. Default + parameterised: Add a default constructor to your Song class that sets title to “Unknown”, artist to “Unknown”, and duration to 0. Test both constructors.

  3. Which constructor?: Given a class with constructors Car(), Car(String make), and Car(String make, int year), state which constructor is called for each:

    • new Car()
    • new Car("Toyota", 2024)
    • new Car("Honda")

Extension

  1. Constructor with validation: Create a Clock class with hours (0-23) and minutes (0-59). The constructor should clamp values that are out of range. Add a getTime() method that returns the time as “HH:MM” format. Test with valid and invalid inputs.

  2. Overloaded constructors: Create a Circle class with a radius attribute. Write three constructors: one that takes a radius, one that takes a diameter (and calculates the radius), and a default that creates a unit circle (radius 1). Add a getArea() method.

Challenge

  1. Design from scenario: A cinema booking system needs a Booking class. A booking can be created with: (a) customer name, movie title, and number of seats; (b) just customer name and movie title (defaults to 1 seat); (c) no arguments (walk-in booking with “Guest” name). Design and implement the class with all three constructors, validation (seats must be 1-10), and a getTotalPrice() method assuming each seat costs $12.50.

Connections

  • Prerequisites: Classes and Objects: understanding what classes and objects are before learning how to initialise them
  • Next: The this Keyword. How constructors distinguish between parameters and instance variables
  • Related: Encapsulation: constructors work with private attributes and validation
  • Forward: Inheritance. Child class constructors call parent constructors with super()

© EduCS.me — A resource hub for Computer Science education

This site uses Just the Docs, a documentation theme for Jekyll.