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:
- Define a class: Create a class that represents the object to be copied.
- Instance variables: Define the attributes (instance variables) of the object.
- Constructor definition: Define a constructor that accepts an object of the same class as a parameter.
- Initialize variables: Use the constructor to assign the values of the instance variables from the passed object to the new object.
- Use
this
keyword: Within the constructor, use thethis
keyword to differentiate between the instance variables of the object and the parameter values. - Null check: Optionally, add a check to handle cases where the passed object is
null
to prevent null pointer exceptions. - 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:
- Within the same class: This is done using the
this()
keyword to invoke another constructor from the same class. - 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:
- Internal Constructor Chaining: To manage constructor calls within the same class.
- 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
Related Chapters
- Overview
- Basic Concepts
- Input/Output in Java
- Flow Control in Java
- Operators in Java
- Arrays
- OOPS in Java
- Inheritance
- Abstraction
- Encapsulation
- Polymorphism
- Constructors
- Methods
- Memory Allocation
- Wrapper Classes
- Keywords
- Access Modifiers
- Classes in java
- Packages in Java
- Exceptions in Java
- Multithreading in Java
- Synchronization in Java
- File Handling