Contents

Constructors​

Copy Constructor in Java

Like C++, Java also supports copy constructors, but Java does not automatically provide a default copy constructor if you don’t define one. To understand copy constructors fully, it’s helpful to first understand constructors in Java. A copy constructor in Java allows you to create a new object that is a copy of an existing object.

The general process for implementing a copy constructor involves the following steps:

  1. Define a class: Create a class that represents the object to be copied.
  2. Instance variables: Define the attributes (instance variables) of the object.
  3. Constructor definition: Define a constructor that accepts an object of the same class as a parameter.
  4. Initialize variables: Use the constructor to assign the values of the instance variables from the passed object to the new object.
  5. Use this keyword: Within the constructor, use the this keyword to differentiate between the instance variables of the object and the parameter values.
  6. Null check: Optionally, add a check to handle cases where the passed object is null to prevent null pointer exceptions.
  7. Deep copy (if necessary): If the class contains objects as instance variables, ensure deep copying by creating new instances of those objects.

Example of Copy Constructor in Java

Here is an example of a class called Book implementing a copy constructor:

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

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

    // Copy constructor
    public Book(Book another) {
        this(another.title, another.author, another.pages);
    }

    // Getters for the instance variables
    public String getTitle() { return title; }
    public String getAuthor() { return author; }
    public int getPages() { return pages; }

    @Override
    public String toString() {
        return "Book: " + title + " by " + author + " (" + pages + " pages)";
    }
}

				
			

Example 1: Copy Constructor Demonstration

				
					// Java program demonstrating the copy constructor

class Rectangle {
    private double length, breadth;

    // Parameterized constructor
    public Rectangle(double length, double breadth) {
        this.length = length;
        this.breadth = breadth;
    }

    // Copy constructor
    public Rectangle(Rectangle r) {
        System.out.println("Copy constructor invoked");
        this.length = r.length;
        this.breadth = r.breadth;
    }

    @Override
    public String toString() {
        return "Rectangle with length: " + length + " and breadth: " + breadth;
    }
}

public class Main {
    public static void main(String[] args) {
        // Creating the original object
        Rectangle rect1 = new Rectangle(5, 10);

        // Using the copy constructor
        Rectangle rect2 = new Rectangle(rect1);

        // Reference assignment (no copy)
        Rectangle rect3 = rect2;

        // Output from the copy constructor object
        System.out.println(rect2);
    }
}

				
			

Output:

				
					Copy constructor invoked
Rectangle with length: 5.0 and breadth: 10.0

				
			

In this example, rect2 is created using the copy constructor, while rect3 is merely a reference to rect2, so it doesn’t invoke the copy constructor.

Example 2: Attempt Without a Copy Constructor

In the following example, we don’t explicitly define a copy constructor, which leads to a compilation error.

				
					// Java program without a copy constructor

class Circle {
    private double radius;

    // Constructor
    public Circle(double radius) {
        this.radius = radius;
    }
}

public class Test {
    public static void main(String[] args) {
        // Creating a Circle object
        Circle c1 = new Circle(7.5);

        // Attempting to copy using an undefined copy constructor
        // This will cause a compiler error
        Circle c2 = new Circle(c1);
    }
}

				
			

Output:

				
					Error: constructor Circle in class Circle cannot be applied to given types;
  required: double
  found: Circle

				
			

Constructor Overloading in Java

Constructor Overloading in Java

Java provides support for Constructor Overloading just as it does for method overloading. In constructor overloading, the appropriate constructor is invoked based on the number and type of parameters provided when a new object is created.

Why Do We Need Constructor Overloading?

There are scenarios where we may need to initialize objects in multiple ways. Constructor overloading enables us to do this by providing different constructors for different situations.

For example, in the Thread class, there are multiple constructors. If you don’t need to specify anything, you can use the default constructor. However, if you want to give the thread a specific name, you can use a constructor that accepts a String parameter:

				
					Thread t = new Thread("MyThread");

				
			

Let’s take an example of a Rectangle class that has just one constructor, which accepts two parameters:

				
					class Rectangle {
    double length, breadth;

    // Constructor when both dimensions are specified
    Rectangle(double l, double b) {
        length = l;
        breadth = b;
    }

    // Calculate and return area
    double area() {
        return length * breadth;
    }
}

				
			

As seen above, the Rectangle constructor requires two arguments. This means every time you create a Rectangle object, you must pass these two values.

For instance, the following statement would result in an error:

				
					Rectangle rect = new Rectangle();

				
			

Since the constructor requires two parameters, this line would result in a compilation error because no parameters are passed. What if we wanted to create a rectangle with default dimensions? Or create a square by passing only one dimension? The answer lies in constructor overloading.

Example of Constructor Overloading

Here is an enhanced version of the Rectangle class that uses constructor overloading:

				
					// Java program demonstrating constructor overloading
class Rectangle {
    double length, breadth;

    // Constructor for initializing both dimensions
    Rectangle(double l, double b) {
        length = l;
        breadth = b;
    }

    // Constructor for default dimensions
    Rectangle() {
        length = breadth = 1;  // Default size
    }

    // Constructor for creating a square
    Rectangle(double side) {
        length = breadth = side;
    }

    // Calculate and return area
    double area() {
        return length * breadth;
    }
}

				
			

Driver Code Example:

				
					public class Test {
    public static void main(String[] args) {
        // Creating objects using different constructors
        Rectangle rect1 = new Rectangle(5, 10);
        Rectangle rect2 = new Rectangle();
        Rectangle square = new Rectangle(7);

        double area;

        // Calculate area of the first rectangle
        area = rect1.area();
        System.out.println("Area of rect1: " + area);

        // Calculate area of the second rectangle (default size)
        area = rect2.area();
        System.out.println("Area of rect2: " + area);

        // Calculate area of the square
        area = square.area();
        System.out.println("Area of square: " + area);
    }
}

				
			

Output:

				
					Area of rect1: 50.0
Area of rect2: 1.0
Area of square: 49.0

				
			

Using this() in Constructor Overloading

In constructor overloading, we can use this() to call one constructor from another within the same class. It helps in reusing code and avoiding redundancy.

Here’s an example demonstrating how this() works in constructor overloading:

				
					// Java program demonstrating the use of `this()` in constructor overloading
public class Circle {
    double radius;
    int circleID;

    // Constructor for initializing radius and circleID
    Circle(double r, int id) {
        radius = r;
        circleID = id;
    }

    // Default constructor initializing radius to 1
    Circle() {
        radius = 1;
    }

    // Constructor for initializing only the circleID
    Circle(int id) {
        // Call the default constructor
        this();
        circleID = id;
    }

    public static void main(String[] args) {
        // Creating a Circle object using only circleID
        Circle circle1 = new Circle(5);

        // Display the radius of the newly created circle
        System.out.println(circle1.radius);
    }
}

				
			

Output:

				
					1.0

				
			

As seen in the example above, when a Circle object is created using the constructor with just circleID, the this() statement calls the default constructor, which initializes the radius to 1.

Note:

The call to another constructor using this() must be the first statement in the constructor. The following code would result in a compilation error:

				
					Circle(int id) {
    circleID = id;

    // This would result in an error because `this()` must be the first statement
    this();
}

				
			

Constructor Chaining In Java

Constructor Chaining in Java

Constructor chaining refers to the process of invoking one constructor from another constructor within the same object.

One of the key reasons to use constructor chaining is to avoid code duplication while having multiple constructors (due to constructor overloading) and to make the code easier to read and maintain.

Types of Constructor Chaining:

Constructor chaining can happen in two ways:

  1. Within the same class: This is done using the this() keyword to invoke another constructor from the same class.
  2. From a base class: This is achieved using the super() keyword to call a constructor from the base class.

Constructor chaining commonly occurs when there is inheritance. A subclass constructor always calls the superclass constructor first, ensuring that the initialization of a subclass object begins with the superclass’s data members. This chaining can continue up the inheritance hierarchy.

Why Do We Need Constructor Chaining?

Constructor chaining is useful when you want to handle multiple tasks across multiple constructors. Instead of handling all tasks in a single constructor, you can create separate constructors for each task, linking them together. This approach enhances code readability and makes maintenance easier.

Constructor Chaining within the Same Class Using this() Keyword:

Example 1:

				
					// Java program demonstrating constructor chaining
// within the same class using this() keyword
class Example {
    // No-argument constructor
    Example() {
        // Call another constructor with a single argument
        this(7);
        System.out.println("Default constructor");
    }

    // Constructor with one parameter
    Example(int x) {
        // Call another constructor with two arguments
        this(x, 8);
        System.out.println(x);
    }

    // Constructor with two parameters
    Example(int x, int y) {
        System.out.println(x + y);
    }

    public static void main(String[] args) {
        // Create object, calling the default constructor
        new Example();
    }
}

				
			

Output:

				
					15
7
Default constructor

				
			

Constructor Chaining Rules:

  • The this() call must always be the first statement in a constructor.
  • There must be at least one constructor that doesn’t use this() (in this case, the third constructor).
  • The order in which constructors are chained does not matter.

Changing the Order of Constructor Calls:

Constructor chaining can be achieved in any order.

Example 2:

				
					// Java program demonstrating constructor chaining
// within the same class by changing the constructor order
class Example {
    // No-argument constructor
    Example() {
        System.out.println("Default constructor");
    }

    // Constructor with one parameter
    Example(int x) {
        // Call the no-argument constructor
        this();
        System.out.println(x);
    }

    // Constructor with two parameters
    Example(int x, int y) {
        // Call the constructor with one parameter
        this(x);
        System.out.println(x * y);
    }

    public static void main(String[] args) {
        // Create object, calling the constructor with two parameters
        new Example(5, 6);
    }
}

				
			

Output:

				
					Default constructor
5
30

				
			

Constructor Chaining Across Classes Using super() Keyword:

Example 3:

				
					// Java program demonstrating constructor chaining
// between two classes using super() keyword
class Parent {
    String message;

    // No-argument constructor
    Parent() {
        this("Hello from Parent");
        System.out.println("Parent no-arg constructor");
    }

    // Constructor with one parameter
    Parent(String msg) {
        this.message = msg;
        System.out.println("Parent parameterized constructor");
    }
}

class Child extends Parent {
    // No-argument constructor
    Child() {
        System.out.println("Child no-arg constructor");
    }

    // Constructor with one parameter
    Child(String msg) {
        // Call the Parent class constructor with one parameter
        super(msg);
        System.out.println("Child parameterized constructor");
    }

    public static void main(String[] args) {
        // Create object, calling the parameterized constructor
        Child obj = new Child("Message from Child");

        // Create object, calling the no-argument constructor
        // Child obj = new Child();
    }
}

				
			

Output:

				
					Parent parameterized constructor
Child parameterized constructor

				
			

Using an Init Block for Common Tasks:

In some cases, you may want to execute a common piece of code before any constructor is executed. This is where an initialization block (init block) can be useful. The init block is executed before any constructor and is common to all constructors.

Example 4:

				
					class Sample {
    // Init block executed before any constructor
    {
        System.out.println("Init block");
    }

    // No-argument constructor
    Sample() {
        System.out.println("No-arg constructor");
    }

    // Constructor with one parameter
    Sample(int x) {
        System.out.println(x);
    }

    public static void main(String[] args) {
        // Create object using no-arg constructor
        new Sample();

        // Create object using parameterized constructor
        new Sample(10);
    }
}

				
			

Output:

				
					Init block
No-arg constructor
Init block
10

				
			

Multiple Init Blocks:

When there are multiple init blocks, they are executed in the order in which they are defined.

Example 5:

				
					class Sample {
    // First init block
    {
        System.out.println("First init block");
    }

    // No-argument constructor
    Sample() {
        System.out.println("No-arg constructor");
    }

    // Constructor with one parameter
    Sample(int x) {
        System.out.println(x);
    }

    // Second init block
    {
        System.out.println("Second init block");
    }

    public static void main(String[] args) {
        // Create object using no-arg constructor
        new Sample();

        // Create object using parameterized constructor
        new Sample(20);
    }
}

				
			

Output:

				
					First init block
Second init block
No-arg constructor
First init block
Second init block
20

				
			

Private Constructors and Singleton Classes

Private Constructors and Singleton Class

Just like other methods in Java, constructors can have access modifiers. If a constructor is marked as private, it can only be accessed within the same class. This raises the question: do we need private constructors? The answer is yes, in certain scenarios. Some key cases where private constructors are used are:

  1. Internal Constructor Chaining: To manage constructor calls within the same class.
  2. Singleton Design Pattern: To limit the creation of instances of a class.

What is a Singleton Class?

A singleton class ensures that only one instance of the class is created throughout the application. Once the object is created, further attempts to create additional objects will return the same instance. Singleton classes are frequently used in areas like networking and database connections where maintaining a single resource is critical.

Design Pattern for Singleton Class:

In singleton classes, the constructor is private, so direct instantiation is not possible. Instead, we use a static method (also known as a factory method) to control access to the single instance of the class.

Example:

				
					// Java program demonstrating the Singleton pattern
// using private constructor
class SingletonExample {
    // Static instance of the class
    private static SingletonExample instance = null;
    
    // Sample variable
    public int data = 100;
    
    // Private constructor prevents instantiation from outside
    private SingletonExample() { }
    
    // Method to provide the single instance of the class
    public static SingletonExample getInstance() {
        if (instance == null) {
            instance = new SingletonExample();
        }
        return instance;
    }
}

// Driver Class
public class SingletonDemo {
    public static void main(String[] args) {
        // Retrieve the singleton instance
        SingletonExample obj1 = SingletonExample.getInstance();
        SingletonExample obj2 = SingletonExample.getInstance();
        
        // Modify data through obj1
        obj1.data += 50;
        
        // Output values of obj1 and obj2
        System.out.println("Value of obj1.data = " + obj1.data);
        System.out.println("Value of obj2.data = " + obj2.data);
    }
}

				
			

Output:

				
					Value of obj1.data = 150
Value of obj2.data = 150