Java Empty Constructor

Sheeraz Gul Dec 10, 2023
  1. Empty Constructor in Java
  2. Implicit Usage of Empty Constructors in Java
  3. Explicitly Defined Empty Constructors in Java
  4. Empty Constructors for Serialization in Java
  5. Empty Constructors for Inheritance in Java
  6. Empty Constructor for Dependency Injection in Java
  7. Conclusion
Java Empty Constructor

Addressing implicit usage, explicitly defined empty constructors, inheritance, serialization, and dependency injection with empty constructors in Java serve various purposes, each contributing to code organization, flexibility, and maintainability. Tackling these aspects helps Java developers create more versatile, maintainable, and extensible code.

This tutorial demonstrates the empty constructor in Java and how to use it.

Empty Constructor in Java

In Java, constructors play a crucial role in initializing objects. A constructor is a special method that is invoked when an object is created.

While you can define your own constructors in a class, Java provides a default empty constructor when no explicit constructors are defined. This default constructor is often referred to as the empty constructor or default constructor.

An empty constructor is a special method in a class that takes no parameters.

This constructor doesn’t perform any specific initialization and allows an object to be instantiated without providing any arguments. Empty constructors are often useful for simple cases where no special setup is required during the creation of an object.

The utilization of empty constructors in Java extends across multiple dimensions, offering developers flexibility and control. Whether for quick object creation, customizing default values, building class hierarchies, supporting serialization, or enabling dependency injection, the understanding of empty constructors is pivotal for crafting robust and adaptable Java applications.

Implicit Usage of Empty Constructors in Java

Understanding the implicit usage of the empty constructor is fundamental to Java programming. It becomes especially relevant when creating instances of a class without specifying any constructor arguments.

Let’s consider a simple Java class called Person. This class represents a person with a name and an age.

Initially, we won’t define any constructors, relying on the implicit empty constructor provided by Java.

Example:

public class Person {
  String name;
  int age;

  public static void main(String[] args) {
    // Creating an instance using implicit empty constructor
    Person person = new Person();

    // Setting values
    person.name = "John Doe";
    person.age = 25;

    // Displaying information
    System.out.println("Name: " + person.name);
    System.out.println("Age: " + person.age);
  }
}

In the Person class, we define two instance variables: name (a String) and age (an int).

Notably, we don’t explicitly specify any constructors in the class. In such cases, Java automatically generates a default empty constructor for us.

Moving into the main method, which serves as the program’s entry point, we utilize the new keyword to instantiate an object of the Person class. This process implicitly triggers the empty constructor provided by Java.

Subsequently, we set specific values for the name and age attributes of the Person object, showcasing how the empty constructor allows us the flexibility to initialize object properties after the object’s creation.

Finally, to visually represent our object, we use System.out.println to print the information to the console.

Output:

Name: John Doe
Age: 25

This output confirms that we successfully created a Person object using the implicit empty constructor, set its properties, and displayed the information.

This demonstrates the effectiveness of the empty constructor in creating instances of a class and facilitates smooth interactions with the objects, contributing to the overall readability and simplicity of our code.

Explicitly Defined Empty Constructors in Java

While Java provides a default empty constructor when none is explicitly defined, there are instances where developers may want to take control of the initialization process. This is achieved by explicitly defining an empty constructor within a class.

Consider a Java class named Car. In this class, we will explicitly define an empty constructor.

This gives us greater control over the construction of Car objects.

Example:

public class Car {
  String model;
  int year;

  // Explicitly defined empty constructor
  public Car() {
    // Default values for the attributes
    this.model = "Unknown Model";
    this.year = 0;
  }

  public static void main(String[] args) {
    // Creating an instance using the explicit empty constructor
    Car myCar = new Car();

    // Displaying information
    System.out.println("Model: " + myCar.model);
    System.out.println("Year: " + myCar.year);
  }
}

Within the Car class, we define two instance variables: model, a String, and year, an int.

Notably, we take a deliberate step by explicitly defining an empty constructor for this class. This constructor is articulated as public Car() { ... }, and it plays a crucial role in initializing default values for the model and year attributes.

The use of the this keyword within the constructor ensures that these default values are assigned to the specific instance of the class.

Moving to the main method, the entry point of our program, we instantiate an object of the Car class using the explicitly defined empty constructor. This deliberate choice guarantees that our Car object starts with predefined default values as outlined in the constructor.

Finally, to visualize the state of our object, we employ System.out.println to print information about the myCar object to the console.

Output:

Model: Unknown Model
Year: 0

This output confirms that our Car object, created using the explicitly defined empty constructor, has been initialized with the default values. The explicit definition of an empty constructor empowers developers to set up meaningful defaults and enhances the flexibility and predictability of object creation in Java.

This showcases the significance of the explicitly defined empty constructor in enabling us to govern the default state of objects, ensuring a consistent and predictable behavior, particularly when no values are explicitly provided during the object’s creation.

Empty Constructors for Serialization in Java

Serialization is a crucial concept in Java, allowing objects to be converted into a stream of bytes for storage or transmission. When working with serialized objects, it is common to encounter scenarios where an empty constructor plays a vital role.

Let’s consider a class named Employee that implements the Serializable interface. Serialization often requires that a class has a default, no-argument constructor.

We’ll explore how to leverage an empty constructor in the example below.

Example:

import java.io.*;

public class Employee implements Serializable {
  private static final long serialVersionUID = 1L;

  String name;
  int employeeId;

  // Empty Constructor
  public Employee() {
    // Default values or initialization code can be added here
  }

  public static void main(String[] args) {
    // Creating an Employee object
    Employee employee = new Employee();
    employee.name = "Alice";
    employee.employeeId = 123;

    // Serialize the Employee object
    serializeEmployee(employee);

    // Deserialize and display information
    Employee deserializedEmployee = deserializeEmployee();
    System.out.println("Name: " + deserializedEmployee.name);
    System.out.println("Employee ID: " + deserializedEmployee.employeeId);
  }

  private static void serializeEmployee(Employee emp) {
    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.ser"))) {
      // Writing the object to a file
      out.writeObject(emp);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private static Employee deserializeEmployee() {
    Employee emp = null;
    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.ser"))) {
      // Reading the object from the file
      emp = (Employee) in.readObject();
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    return emp;
  }
}

The Employee class implements the Serializable interface, signifying its objects’ ability to be serialized. The serialVersionUID serves as a version control field, aiding compatibility between serialized and deserialized versions.

An essential element for serialization, the class features an empty constructor. During deserialization, this no-argument constructor is instrumental in recreating an object before restoring its state.

The serializeEmployee method handles the serialization process by writing the Employee object to a file named employee.ser using ObjectOutputStream.

On the flip side, the deserializeEmployee method reads the serialized object from the file, utilizing ObjectInputStream to return an Employee object.

In the main method, we instantiate an Employee object, set its properties, serialize it, and subsequently deserialize it. The displayed information confirms the seamless success of both serialization and deserialization processes.

Output:

Name: Alice
Employee ID: 123

This output confirms that the Employee object has been successfully serialized, written to a file, deserialized, and its information displayed. The empty constructor plays a crucial role in this process, ensuring the proper instantiation of the object during deserialization.

This comprehensive flow illustrates the significance of an empty constructor in facilitating the serialization and deserialization of Java objects.

Empty Constructors for Inheritance in Java

In Java, constructors are vital for initializing objects, and understanding their behavior in the context of inheritance is crucial. When a subclass extends a superclass, constructors play a pivotal role in the object’s creation process.

We will explore the role of empty constructors and default constructors in the inheritance hierarchy below.

Example:

class Animal {
  String species;

  // Empty constructor for Animal
  public Animal() {
    this.species = "Unknown";
  }

  // Parameterized constructor for Animal
  public Animal(String species) {
    this.species = species;
  }

  // Method to display information
  public void displayInfo() {
    System.out.println("Species: " + species);
  }
}

class Dog extends Animal {
  String breed;

  // Empty constructor for Dog
  public Dog() {
    // Implicit call to the empty constructor of the superclass (Animal)
    this.breed = "Unknown Breed";
  }

  // Parameterized constructor for Dog
  public Dog(String species, String breed) {
    // Explicit call to the parameterized constructor of the superclass (Animal)
    super(species);
    this.breed = breed;
  }

  // Method to display additional information for Dog
  public void displayDogInfo() {
    System.out.println("Breed: " + breed);
  }
}

public class Main {
  public static void main(String[] args) {
    // Creating instances using empty and parameterized constructors
    Animal genericAnimal = new Animal();
    Dog genericDog = new Dog();
    Animal cat = new Animal("Cat");
    Dog germanShepherd = new Dog("Dog", "German Shepherd");

    // Displaying information
    System.out.println("Generic Animal:");
    genericAnimal.displayInfo();

    System.out.println("\nGeneric Dog:");
    genericDog.displayInfo(); // Inherits displayInfo() from Animal
    genericDog.displayDogInfo();

    System.out.println("\nCat:");
    cat.displayInfo();

    System.out.println("\nGerman Shepherd Dog:");
    germanShepherd.displayInfo(); // Inherits displayInfo() from Animal
    germanShepherd.displayDogInfo();
  }
}

The code defines two classes: Animal and Dog.

The Animal class manages the species of an animal with an instance variable and provides both empty and parameterized constructors for initialization.

The Dog class extends Animal to represent a dog, inheriting its properties. The empty constructor in Animal defaults the species to Unknown when creating an instance.

Additionally, there’s a parameterized constructor in Animal enabling species customization. The displayInfo() method in Animal prints the species to the console.

In the Dog class, the empty constructor implicitly calls the superclass’s empty constructor, setting the default breed for a dog to Unknown Breed.

The parameterized constructor in Dog explicitly calls the superclass’s parameterized constructor, allowing customization of both species and breed. The displayDogInfo() method in Dog prints the breed-specific information.

In the main method, instances of both classes are created using empty and parameterized constructors, demonstrating the inheritance and functionality of constructors.

Output:

Generic Animal:
Species: Unknown

Generic Dog:
Species: Unknown
Breed: Unknown Breed

Cat:
Species: Cat

German Shepherd Dog:
Species: Dog
Breed: German Shepherd

The output illustrates the behavior of the constructors and the inheritance relationship between Animal and Dog instances.

The displayInfo() method inherited by Dog from Animal demonstrates how the superclass’s methods are accessible to the subclass. This showcases the effectiveness of empty constructors for providing default values and facilitating inheritance in Java.

This example illustrates the simplicity and effectiveness of using empty constructors in Java for default initialization and inheritance.

Empty Constructor for Dependency Injection in Java

Dependency Injection (DI) is a powerful design pattern in Java that promotes loose coupling and enhances the testability and maintainability of code. At the heart of DI is the concept of injecting dependencies into a class rather than having the class create them internally.

The example below will involve an Animal class and a Zoo class where animals are injected into the zoo.

Example:

// Animal class representing a generic animal
class Animal {
  private String species;

  // Empty constructor for Animal
  public Animal() {
    this.species = "Unknown";
  }

  // Parameterized constructor for Animal
  public Animal(String species) {
    this.species = species;
  }

  // Getter method for species
  public String getSpecies() {
    return species;
  }
}

// Zoo class representing a zoo that contains animals
class Zoo {
  private Animal resident;

  // Empty constructor for Zoo (dependency injection)
  public Zoo() {
    // Animal is not instantiated internally; it will be injected
  }

  // Setter method for injecting an Animal into the Zoo
  public void setResidentAnimal(Animal animal) {
    this.resident = animal;
  }

  // Method to display information about the Zoo and its resident animal
  public void displayZooInfo() {
    System.out.println("Welcome to the Zoo!");
    if (resident != null) {
      System.out.println("Our resident animal is a " + resident.getSpecies());
    } else {
      System.out.println("No resident animal yet.");
    }
  }
}

// Main class to demonstrate the code
public class Main {
  public static void main(String[] args) {
    // Create an instance of Zoo
    Zoo myZoo = new Zoo();

    // Create instances of Animal
    Animal lion = new Animal("Lion");
    Animal elephant = new Animal("Elephant");

    // Inject animals into the zoo
    myZoo.setResidentAnimal(lion);

    // Display information about the Zoo
    myZoo.displayZooInfo();

    // Inject another animal into the zoo
    myZoo.setResidentAnimal(elephant);

    // Display updated information about the Zoo
    myZoo.displayZooInfo();
  }
}

We have two classes, Animal and Zoo. The Animal class has an empty constructor and a parameterized constructor to represent different species of animals.

The Zoo class has an empty constructor, representing dependency injection. It also has a setter method (setResidentAnimal()) for injecting an Animal into the zoo and a display method (displayZooInfo()) to show information about the zoo and its resident animal.

In the Main class, we create an instance of the Zoo class (myZoo). We also create instances of Animal representing a lion and an elephant.

We then inject the lion into the zoo using the setResidentAnimal() method and display information about the zoo. Later, we inject an elephant and display the updated information.

Output:

Welcome to the Zoo!
Our resident animal is a Lion
Welcome to the Zoo!
Our resident animal is an Elephant

This output demonstrates the use of an empty constructor in the Zoo class for dependency injection. The Zoo class doesn’t internally instantiate an Animal but relies on the setResidentAnimal() method to inject an Animal instance.

This approach allows flexibility in adding and changing resident animals in the zoo without modifying the Zoo class itself, showcasing the effectiveness of empty constructors for dependency injection in Java.

Conclusion

In conclusion, exploring the usage of empty constructors in Java reveals their versatility and significance in various aspects of software development.

Addressing implicit usage, explicitly defined empty constructors, inheritance, serialization, and dependency injection collectively contribute to improved code organization, flexibility, and maintainability.

The understanding and effective utilization of empty constructors empower Java developers to create more versatile, maintainable, and extensible code, enhancing the overall quality of Java applications.

Author: Sheeraz Gul
Sheeraz Gul avatar Sheeraz Gul avatar

Sheeraz is a Doctorate fellow in Computer Science at Northwestern Polytechnical University, Xian, China. He has 7 years of Software Development experience in AI, Web, Database, and Desktop technologies. He writes tutorials in Java, PHP, Python, GoLang, R, etc., to help beginners learn the field of Computer Science.

LinkedIn Facebook

Related Article - Java Constructor