Introduction to Constructor in OOPS (Java)

Constructors are fundamental concepts in OOP. We use it for object creation, initialization and maintaining the integrity and consistency of objects throughout their lifecycle. Other than this, it is a core part of several object-orientated principles and design patterns.

What is Constructor?

In object-oriented programming, constructors are methods (with the same name as the class) that is used to create and initialize an object of that class. When we create a new object, the constructor is called, and necessary initialization is performed.

  • Constructors ensure that objects are properly set up and ready to use. It can also perform certain operations every time an object is created.
  • Constructors have no return type. If we provide a return value, the compiler will not treat that method as a constructor.

In the following example, we have defined the constructor for the class MobilePhone.

public class MobilePhone {
    private int imeiNumber;    
    private String brandName;
    
    // Constructor
    public MobilePhone (int number, String name) {
        // Initialization code
        imeiNumber = number;
        brandName = brand;
    }
    public String getBrandName() {
        return brandName;
    }
    
    public int getImeiNumber() {
        return imeiNumber;
    }
}

Here's an example of how we create the object of the MobilePhone class using its constructor.

MobilePhone myPhone = new MobilePhone(12345678, "Samsung");

The new keyword creates a new instance of the MobilePhone class and allocates the required memory. Then the constructor will call itself by passing the arguments in the parameter list.

In Java, a constructor has the following elements:

  • Access specifier: Determines the visibility and accessibility of the constructor.
  • Constructor name: This must be the same as the class name.
  • Parameter list: Used to pass the necessary information to initialize the object.
  • Constructor body: A code to be executed when the constructor is called. This can include any operations or assignments needed to set up the object.

Types of Constructors in Java

In Java, there are four types of constructors:

Default constructor (provided by the compiler)

A default constructor is automatically provided by the Java compiler if a class does not have any constructors defined. It is a no-argument constructor because it does not take any arguments.

  • Default constructor initializes the object's properties with default values: 0 for numeric types, null for reference types, and false for boolean types.
  • There is always one constructor that exists in the class, regardless of whether we write a constructor ourselves. But it is good programming practice to always include at least one constructor in a class, even if we plan to do nothing inside it. If required, we can add some logic later.
  • Behind the scene, the default constructor calls the constructor of its superclass. In many cases, the superclass will be part of the language framework, like the Object class in Java.
  • If a class has other constructors, the Java compiler will not provide a default constructor. In this case, developers can create a default constructor themselves if it is needed. Note: There can be some maintenance issues if we depend on the default constructor. For example, if we add another constructor later in the class, then the default constructor is not created!

No-argument constructor

In Java, a no-argument constructor does not take any parameters. This means: When an object is created without passing any arguments to the constructor, the no-argument constructor is called. This can be useful when we want to perform initialization with some default values or perform certain setup operations every time an object is created.

Example

public class MobilePhone {
    ......
    public MobilePhone() {
        //perform some operations
    }
    .....
}

Parameterized constructor

In Java, a parameterized constructor takes one or more parameters. This will allow developers to initialize an object with specific values.

  • A class can have multiple parameterized constructors with different parameter lists. This is constructor overloading (discussed below in this blog).
  • Parameterized constructors enable customization and uniqueness among the objects.

Example

public class MobilePhone {
    private int imeiNumber;    
    private String brandName;
  
    //parameterized constructor 1
    public MobilePhone (int number, String name) {
        imeiNumber = number;
        brandName = brand;
    }
  
    //parameterized constructor 2
    public MobilePhone (int number) {
        imeiNumber = number;
    }
  
    //.....
}

Copy constructor

In Java, a copy constructor creates a new object by copying the values of another object of the same class. It takes an object of the same class as a parameter to initialize the properties of the new object.

  • It can perform either a shallow copy or a deep copy. In a shallow copy, only the references to the data of the original object are copied, while in a deep copy, the entire contents of the original object are duplicated.
  • Java doesn't create a default copy constructor. So, It is up to the developer to define and implement the copy constructor if needed.
  • This can be useful when we want to create a copy of an existing object but don't want the new object to share the same memory location as the existing object.

Example

class MobilePhone {
    private int imeiNumber;    
    private String brandName;
    //copy constructor
    MobilePhone (MobilePhone object){
        this.imeiNumber = object.imeiNumber;
        this.brandName = object.brandName;
    }
    ......
}

Private constructor

It is possible to have both private and public constructors in a class. A private constructor restricts the visibility of the constructor to only within the class itself.

  • It is often used when we want to prevent the direct instantiation of a class from external classes.
  • It is used in utility classes that provide static methods and do not need to be instantiated.

The good use case of private constructors is singleton design patterns, where we control the creation of a single instance of a class using the private constructor.

Constructor overloading

In Java, constructor overloading is the ability of a class to have multiple constructors with different parameter lists. This means: A class can have several constructors with the same name but different types and/or numbers of parameters.

  • When a class has multiple constructors, the Java compiler uses the arguments passed to the constructor to determine which constructor to call. This process is known as constructor resolution.
  • This can be useful for creating objects with different initial property values or for providing different levels of complexity for object creation.

Example 1

class MobilePhone {
    private int imeiNumber;    
    private String brandName;
     
    MobilePhone(){
        .....
    }
    
    MobilePhone(int number, String name){
        .....
    }
    
    MobilePhone(MobilePhone mobile){
        .....
    }
    
    MobilePhone(int number) {
        .....
    }
    
    ...
}

Constructor chaining

In Java, constructor chaining is the practice of calling one constructor from another constructor within the same class or from a subclass constructor to a superclass constructor.

In the same class, we can achieve constructor chaining by using the "this" keyword followed by the parameters required by the constructor being called.

For example, suppose you have a class called "Person" with constructors that take different sets of arguments. Here second constructor "Person(String name, int age, String address)" is calling the first constructor "Person(String name, int age)" using this(name, age).

class Person {
    private String name;
    private int age;
    private String address;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public Person(String name, int age, String address) {
        this(name, age); // calls the constructor above
        this.address = address;
    }
    //...
}
  • The call to another constructor must be the first statement in the constructor. So you can create an object of the Person class by either of the constructors.
  • It allows constructors with different sets of parameters to reuse code and avoid duplication by delegating initialization tasks to other constructors.
Person p1 = new Person("Shubham", 30);
Person p2 = new Person("Shubham", 30, "New Delhi");

In the inheritance hierarchy, we use the "super" keyword to invoke a constructor of the superclass from the subclass. Here, super() must also be the first line in the constructor body.

  • We use this to call the superclass constructor to initialize the inherited members of the subclass.
  • This ensures that all necessary initialization steps from the superclass are performed before executing the subclass-specific initialization.

Explore more about constructor chaining in the inheritance blog.

class Vehicle {
    private String brand;

    public Vehicle(String brand) {
        this.brand = brand;
    }
    
    //....
}

class Car extends Vehicle {
    private int numberOfSeats;

    public Car(String brand, int numberOfSeats) {
        super(brand); // Invoking superclass constructor using "super"
        this.numberOfSeats = numberOfSeats;
    }
    
    //...
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car("Toyota", 5);
        car.displayDetails();
    }
}

Some popular use cases of constructors

Implementing dependency injection pattern

In Java, we use constructors to implement dependency injection by passing in the required dependencies as parameters to the constructor. For example, consider a class Car that has a dependency on an Engine object. Instead of creating an Engine object inside the Car class, the Engine object can be passed in as a parameter to the Car class's constructor. This way, the Car class does not have to worry about creating its own Engine object.

class Engine { ... }

class Car {
    private Engine engine;
    public Car(Engine engine) {
        this.engine = engine;
    }
    ...
}

Implementing singleton pattern

The singleton pattern ensures that a class has only one instance and provides a global access point to that instance. So to implement the singleton pattern, we use a private constructor to ensure a single instance of the class. Here is an example:

public class Singleton {
    private static Singleton instance;
    private Singleton(){}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Inside the getInstance() method, we create an instance of the class using a private constructor if it doesn't already exist. So, one can only call the getInstance() method to access or instantiate the single instance of the class.

Implementing a secure authentication process to create an object

We can use a private constructor to enforce security by implementing a user authentication process before creating an object. For example, in the following code, SecureObject class is only instantiated when the user credentials are valid. This will provide an additional layer of security to the application.

public class SecureObject {
    private SecureObject() {
        // private constructor
    }
    public static SecureObject createInstance(String username, String password) {
        // Verify user credentials
        boolean isAuthenticated = authenticate(username, password);
        if (isAuthenticated) {
            return new SecureObject();
        } else {
            throw new SecurityException("Invalid credentials");
        }
    }
    private static boolean authenticate(String username, String password) {
        // check credentials against a database or external service
        // return true if valid, false otherwise
    }
}

Driver code

SecureObject obj = SecureObject.createInstance("username", "password");

Other possible use cases of constructors

  • To initialize the properties of an object.
  • To set default values for the properties of an object.
  • For creating a deep copy of an object.
  • To start a thread or initialize other resources when an object is created.
  • To register an object with an event listener or observer.
  • To set up a connection to a remote service or database.
  • To implement factory patterns
  • To implement the builder pattern
  • To create immutable objects that cannot be modified once they are created.

Some critical ideas to think!

  • What is the significance of the final keyword in a constructor parameter?
  • What is the purpose of the static keyword in a constructor?
  • Can a constructor call itself recursively? Why or why not?
  • How are constructors used in inheritance?
  • Can you prevent a class from being instantiated using constructors?

Conclusion

The idea of the constructor is used everywhere in OOPS because object creation and initialization are core parts of object-oriented design. For example, all creational design patterns use the idea to simplify the idea of object instantiation. So, learning various use cases, tradeoffs and properties related to constructors is important.

Enjoy learning, Enjoy oops!

More from EnjoyAlgorithms

Self-paced Courses and Blogs