Mastering SUPER Keyword in Java: Unlocking Inheritance and Constructor Chaining

Arshi Saxena - Oct 17 - - Dev Community

In this post, we’ll dive deep into the super keyword and explore how it works in different scenarios. The first part covers using super to access parent class methods and variables, while the second part explains constructor chaining, including all nuances involved.


1. Using super to Access Parent Class Methods and Variables

The super keyword allows child classes to reuse functionality from the parent class, including methods and variables. It’s particularly useful when you want to:

  1. Call the parent class’s overridden methods.
  2. Access hidden variables when a child class has the same variable name as the parent class.

Example Code:

Parent Class

package super_keyword;

class EmployeeParent {
    private int id, depId;
    protected String name;
    private double basicSal;

    public EmployeeParent(int id, String name, int depId, double basicSal) {
        this.id = id;
        this.name = name;
        this.depId = depId;
        this.basicSal = basicSal;
    }

    // Parent method to compute net salary
    protected double computeNetSalary() {
        return basicSal;
    }

    @Override
    public String toString() {
        return "EmployeeParent [id=" + id + ", depId=" + depId + ",
        name=" + name + ", basicSal=" + basicSal + "]";
    }
}
Enter fullscreen mode Exit fullscreen mode

Child Class

class ManagerChild extends EmployeeParent {
    private double perfBonus;

    public ManagerChild(int id, String name, int depId, double basicSal,
    double perfBonus) {
        super(id, name, depId, basicSal);  // Calling Parent Constructor
        this.perfBonus = perfBonus;
    }

    // Overriding Parent Method
    @Override
    public double computeNetSalary() {
        // Using super to call parent class method
        /* Without using super keyword here, compiler assumes that we are 
        using recursion and ends up with exception StackOverflowError. */
        return perfBonus + super.computeNetSalary();  
    }

    @Override
    public String toString() {
        // Using super to reuse parent’s toString()
        return "ManagerChild [perfBonus=" + perfBonus + ", " +
        super.toString() + "]";
    }

    public void displayParentName() {
        // Accessing parent’s variable
        System.out.println("Employee's Name: " + super.name);
    }

    public static void main(String[] args) {
        ManagerChild manager = new ManagerChild(1, "Arshi", 2, 10000, 1890);
        System.out.println(manager);
        System.out.println("Net Salary: " + manager.computeNetSalary());
        manager.displayParentSalary();
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. Accessing Parent Method Using super:

    • In computeNetSalary(), super.computeNetSalary() invokes the parent class’s version of the method.
    • In Child's toString(), super.toString() invokes parent class’s version of the method.
  2. Accessing Parent Variable Using super:
    The displayParentName() method demonstrates how to access a parent class’s variable (name) using super.

  3. Calling Parent’s Constructor with super():
    We use super(id, name, ...) to initialize common fields by invoking the parent class constructor.


2. Constructor Chaining with super()

Constructor chaining is a powerful feature in Java that allows a subclass constructor to invoke a parent class constructor, ensuring that the parent class is properly initialized before executing the child class's constructor.

The super() keyword plays a crucial role in this process. Let's explore the nuances of constructor chaining and how it operates.

Key Concepts of Constructor Chaining:

1. There will always be a call to ANY Parent Constructor from ALL Child Constructors:

In Java, every child class constructor has to call a constructor from its parent class. This is crucial for establishing the proper initialization sequence in a class hierarchy.

Example:

   package constructor_chaining;

   public class ConstructorChainingParent {
       // Parent Default Constructor
       public ConstructorChainingParent() {
           System.out.println("Parent DEFAULT Constructor Called");
       }

       // Parent constructor with one parameter
       public ConstructorChainingParent(String name) {
           System.out.println("Parent PARAMETERIZED Constructor Called
           with name: " + name);
       }

       // Parent constructor with two parameters
       public ConstructorChainingParent(String name, int id) {
           System.out.println("Parent PARAMETERIZED Constructor Called
           with name: " + name + " and id: " + id);
       }
   }

   public class ConstructorChainingChild extends ConstructorChainingParent {
       // Child Default Constructor
       public ConstructorChainingChild() {
           // Compiler inserts super() automatically
           System.out.println("Child DEFAULT Constructor Called");
       }

       // Child constructor with one parameter
       public ConstructorChainingChild(String name) {
           super(name); // Calling parent constructor with one parameter
           System.out.println("Child PARAMETERIZED Constructor Called
           with name: " + name);
       }

       // Child constructor with two parameters
       public ConstructorChainingChild(String name, int id) {
           // Calling parent constructor with two parameters
           super(name, id); 
           System.out.println("Child PARAMETERIZED Constructor Called
           with name: " + name + " and id: " + id);
       }

       public static void main(String[] args) {
           // Creating an instance of the child class
           ConstructorChainingChild child = new ConstructorChainingChild();
           // Output:
           // Parent DEFAULT Constructor Called
           // Child DEFAULT Constructor Called

           // Creating an instance of the child class with one parameter
           ConstructorChainingChild child1 = new ConstructorChainingChild("Alice");
           // Output:
           // Parent PARAMETERIZED Constructor Called with name: Alice
           // Child PARAMETERIZED Constructor Called with name: Alice

           // Creating an instance of the child class with two parameters
           ConstructorChainingChild child2 = new ConstructorChainingChild("Bob", 1);
           // Output:
           // Parent PARAMETERIZED Constructor Called with name: Bob and id: 1
           // Child PARAMETERIZED Constructor Called with name: Bob and id: 1
       }
   }
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. Explicit Calls to Parent Constructors:

    • In this example, both parameterized constructors in the ConstructorChainingParent class are called explicitly by the constructors of the ConstructorChainingChild class.
    • This demonstrates that invoking a parent constructor is mandatory in child constructors.
  2. Implicit Call to Default Constructor:

    • Since there was no explicit call to the ConstructorChainingParent class's default constructor, it may appear that the rule is being broken.
    • However, this is acceptable because the compiler automatically inserts a call to the parent class' default constructor using super().
    • This implicit behavior occurs when a child class constructor does not explicitly call any parent class constructor.

2. If there are NO parent constructors, the compiler implicitly inserts a default constructor. This default constructor gets called from child class constructors implicitly.

In Java, when there are NO constructors defined in a class, the compiler automatically provides an implicit default constructor that initializes instance variables to their default values. This default constructor is called behind the scenes when a child class constructor is invoked without an explicit call to a parent constructor using super().

Example:

package constructor_chaining;

public class ConstructorChainingParent {
    // No constructor defined
    // The compiler provides an implicit default constructor.
}

public class ConstructorChainingChild extends ConstructorChainingParent {

    public ConstructorChainingChild() {
        // Compiler inserts super() behind the scenes
        // Calls the compiler-provided parent class's default constructor 
        System.out.println("Child DEFAULT Constructor Called");
    }

    public static void main(String[] args) {
        // Creating an instance of the child class
        ConstructorChainingChild child = new ConstructorChainingChild();
        // Output:
        // Child DEFAULT Constructor Called
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, since there are NO constructors defined in the ConstructorChainingParent, the compiler automatically provides a default constructor. When the ConstructorChainingChild is instantiated, it implicitly calls this default constructor, ensuring that the parent class is properly initialized, even though we don't see the super() call in the child's constructor.


3. If parent class only has parameterized constructors, compiler DOESN'T provide a default constructor. Thus, Calling Parent Parameterized Constructor from ALL child constructors becomes mandatory.

In cases where the parent class does not provide a default constructor and has only parameterized constructors, the compiler does not automatically insert a default constructor.

Consequently, all child class constructors must explicitly call one of the parent class's parameterized constructors as there is NO default constructor.

Example:

package constructor_chaining;

public class ConstructorChainingParent {
    // Parent Parameterized Constructor
    public ConstructorChainingParent(String name) {
        System.out.println("Parent PARAMETERIZED Constructor Called with
        name: " + name);
    }
}

public class ConstructorChainingChild extends ConstructorChainingParent {
    // Child Default Constructor
    public ConstructorChainingChild() {
        /* Must call parent parameterized constructor,
         * Otherwise, Compile Error occurs
         * Error: ConstructorChainingParent requires a String parameter
        */
        super("Default Name");
        System.out.println("Child DEFAULT Constructor Called");
    }

    // Child Parameterized Constructor
    public ConstructorChainingChild(String name) {
        // Calling parent constructor from ALL child constructors
        super(name);
        System.out.println("Child PARAMETERIZED Constructor Called with
        name: " + name);
    }

    public static void main(String[] args) {
        // Creating an instance of the child class
        ConstructorChainingChild childDefault = new ConstructorChainingChild();
        // Output:
        // Parent PARAMETERIZED Constructor Called with name: Default Name
        // Child DEFAULT Constructor Called

        // Creating an instance of the child class with the parameterized constructor
        ConstructorChainingChild childParam = new ConstructorChainingChild("Alice");
        // Output:
        // Parent PARAMETERIZED Constructor Called with name: Alice
        // Child PARAMETERIZED Constructor Called with name: Alice
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. Parameterized Constructor in Parent Class:
    The ConstructorChainingParent class only provides a parameterized constructor with a String parameter.

  2. Compiler Does Not Provide a Default Constructor:

    • Since a parameterized constructor is explicitly defined, the compiler does not provide an implicit default constructor.
    • This means that if a child class does not explicitly call any parent constructor, the compiler cannot insert super() to call a non-existent default constructor, leading to a compile-time error.
  3. Mandatory Call to Parameterized Constructor:
    All child class constructors must explicitly call one of the parent’s parameterized constructors using super() to avoid a compile-time error.

  4. Demonstration in Example Code:

    • The default constructor in ConstructorChainingChild calls the parent’s parameterized constructor with "Default Name".
    • The parameterized constructor in ConstructorChainingChild also calls the parent constructor with the provided name parameter.

This ensures that the child class properly initializes the parent class's state with the required data.


3. Using this() and super() in Constructors

If you are new to this keyword, I recommend checkout out Mastering THIS Keyword in Java: A Key to Clean and Effective Code first to better understand this section.

Both this() and super() are special calls used to invoke constructors, but they follow specific rules that impact their usage:

  1. this() and super() Must Be the First Statement in a Constructor:

    • When using this() to call another constructor within the same class or super() to invoke a parent class constructor, the call must always be the first statement in the constructor body.
  2. this() and super() Cannot Be Used Together in the Same Constructor:

    • Since only one of them can occupy the first statement position, it is impossible to call both this() and super() within the same constructor.
    • This restriction ensures the proper initialization sequence is maintained either within the same class hierarchy (this()) or across parent-child classes (super()).

These rules highlight how Java enforces constructor chaining in a structured manner to ensure consistent initialization at every level.


Key Takeaways

  1. Accessing Parent Class Methods and Variables:

    • The super keyword allows a child class to reuse the parent class’s methods and variables.
    • It is useful for calling overridden methods and accessing hidden variables from the parent class.
  2. Constructor Chaining with super():

    • Every child class constructor must invoke a parent class constructor, ensuring that parent class initialization occurs before the child’s logic executes.
    • If the parent class defines only parameterized constructors, child class constructors must explicitly call one of them, as the compiler won’t insert a default constructor.
  3. this() vs super() in Constructors:

    • Both this() and super() must be the first statement in a constructor.
    • They cannot coexist in the same constructor, enforcing structured constructor chaining within and across class hierarchies.

Conclusion

Understanding how to use super effectively enhances your ability to write clean, well-structured, and maintainable Java code, especially in complex inheritance scenarios. Mastery of these concepts is also crucial for interviews, as it demonstrates your grasp of key object-oriented principles.

Stay tuned for forthcoming posts in this series, where we’ll explore other keywords and delve into their nuances.🚀


Related Posts

Happy Coding!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .