How to Use the super Keyword in Java (With Examples)
In Java programming, inheritance is a fundamental concept that allows one class to inherit fields and methods from another. This relationship between classes promotes code reuse and hierarchical organization of classes. The super keyword plays a vital role in this context. It serves as a reference to the immediate parent class (or superclass) of a subclass. Using super, a subclass can explicitly call methods, access fields, and invoke constructors of its superclass.
The super keyword helps resolve conflicts that arise when a subclass overrides a method or hides a variable inherited from its superclass. It also ensures proper initialization of inherited members through constructor chaining. In this section, we will introduce the basics of the super keyword, explain why it is essential, and set the stage for deeper exploration in the following parts.
The super keyword in Java acts as a direct link to the parent class of an object. When a class inherits from another, it gains access to the parent’s fields and methods, but sometimes these are overridden or hidden in the subclass. The super keyword provides a way to access the original version from the parent.
Using super, you can:
This keyword helps maintain clarity in code, ensures proper reuse of functionality, and avoids ambiguity when dealing with inheritance hierarchies.
Object-oriented programming (OOP) revolves around concepts like inheritance, encapsulation, polymorphism, and abstraction. Inheritance allows classes to be related by hierarchical structures, enabling subclasses to reuse and extend the functionality of superclasses. However, inheritance can create scenarios where subclass members overshadow or override superclass members.
Without the super keyword, it would be difficult to reference or invoke the original superclass members in cases where they have been overridden. The super keyword makes it explicit and straightforward to refer to these superclass elements, facilitating:
By supporting constructor chaining, super also ensures that the entire inheritance chain is properly initialized, maintaining the integrity of the object’s state.
One of the primary uses of the super keyword is to access fields and methods of the superclass from the subclass. This is particularly important when the subclass defines a field or method with the same name as one in the superclass, a situation known as name hiding or method overriding.
If a subclass declares a variable with the same name as a variable in the superclass, the subclass variable hides the superclass variable. To refer to the superclass variable explicitly, you use super.variableName. This clarifies which variable you intend to use and helps avoid confusion.
Example:
java
CopyEdit
class Parent {
int number = 100;
}
class Child extends Parent {
int number = 200;
void display() {
System.out.println(“Child number: ” + number); // refers to Child’s number
System.out.println(“Parent number: ” + super.number); // refers to Parent’s number
}
}
In this example, super. Number refers to the number variable declared in the parent class, while number alone refers to the variable declared in the child class.
When a subclass overrides a method from the superclass, the subclass’s version is called by default. However, if the subclass wants to extend or supplement the behavior of the superclass method rather than completely replacing it, it can call the superclass method explicitly using super.methodName().
Example:
java
CopyEdit
class Parent {
void display() {
System .out.println(“Parent display method”);
}
}
class Child extends Parent {
void display() {
super.display(); // calls Parent’s display method
System. out.println(“Child display method”);
}
}
Here, the child’s display method calls the parent’s display method using super.display(), then adds its functionality. This pattern is common when subclass behavior is an extension of superclass behavior.
Another essential use of the super keyword is to invoke constructors of the superclass from within subclass constructors. In Java, when an object of a subclass is created, the constructor of the superclass is automatically called first. If the superclass has a no-argument constructor, it is called by default. However, if the superclass only has parameterized constructors, or if you want to call a specific constructor, you need to use super() with the appropriate arguments.
Example:
java
CopyEdit
class Parent {
Parent(int age) {
System. out.println(“Parent constructor called with age: ” + age);
}
}
class Child extends Parent {
Child() {
super(50); // calling parameterized constructor of Parent
System .out.println(“Child constructor called”);
}
}
In this example, the Child class constructor explicitly calls the parameterized constructor of the Parent class using super(50).
Constructor chaining refers to the practice where a constructor calls another constructor to reuse initialization code. This chaining ensures that all necessary parts of an object’s state, especially inherited fields, are properly initialized.
By using super(), a subclass constructor ensures that the parent class is initialized before the subclass completes its initialization. This is critical in avoiding partially initialized objects and preserving class invariants.
In complex class hierarchies, subclasses often define fields or methods with the same name as their superclasses. Without a way to differentiate, it would be difficult to specify which member should be accessed or invoked.
The super keyword provides a clear way to resolve these naming conflicts by explicitly referring to the superclass’s version. This helps maintain clean, understandable code, especially in large projects where classes may have many layers of inheritance.
For example, if a superclass and subclass both have a method named calculate, calling calculate() within the subclass will execute the subclass’s version. To access the superclass’s version, the subclass must call super.calculate().
The super keyword in Java is a crucial tool for handling inheritance-related tasks. It allows subclasses to:
By pr. Providing a direct link to the parent class, super helps maintain clean, organized, and efficient code that leverages inheritance fully.
The super keyword is fundamental in Java inheritance, but its uses extend beyond the basic scenarios discussed earlier. Understanding the advanced concepts surrounding super can greatly improve how you design and implement class hierarchies. This section dives into more nuanced use cases, common patterns, and best practices for using super.
One of the most frequent reasons to use super is within method overriding. Overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This is useful for customizing or enhancing inherited behavior.
When a subclass overrides a method, the original method in the superclass is hidden unless explicitly invoked using super. There are several reasons why you might want to call the superclass method inside the overridden method:
Consider a graphical user interface (GUI) component where a base class defines a draw() method, and subclasses override it to add specific features.
java
CopyEdit
class Component {
void draw() {
System. out.println(“Drawing basic component”);
}
}
class Button extends Component {
void draw() {
super.draw(); // calls Component’s draw
System. out.println(“Drawing button-specific features”);
}
}
Calling super.draw() ensures the base drawing logic is executed before adding button-specific drawing code.
When a subclass declares a variable with the same name as a variable in its superclass, the subclass’s variable hides the superclass variable. Accessing the superclass variable requires using super.
java
CopyEdit
class Animal {
String name = “Animal”;
}
Class Dog extends Animal {
String name = “Dog”;
void printNames() {
System.out.println(name); // Dog’s name
System.out.println(super.name); // Animal’s name
}
}
In this case, super. Name accesses the name field of the Animal class, while name refers to the subclass’s variable.
Calling superclass constructors using super() is a vital part of creating robust and maintainable class hierarchies. Beyond simple invocation, understanding the rules and design considerations related to super() helps avoid common pitfalls.
If the superclass provides multiple overloaded constructors, the subclass can choose which one to call by specifying the appropriate parameters.
java
CopyEdit
class Person {
Person() {
System. out.println(“Person default constructor”);
}
Person(String name) {
System.out.println(“Person constructor with name: ” + name);
}
}
class Employee extends Person {
Employee() {
super(“John Doe”); // calls Person(String)
System .out.println(“Employee default constructor”);
}
}
Here, the Employee constructor explicitly calls a parameterized constructor of the superclass.
In multilevel inheritance, constructors chain upward through all superclass constructors. Each constructor must ensure proper initialization by invoking its superclass constructor using super().
java
CopyEdit
class Grandparent {
Grandparent() {
System. out.println(“Grandparent constructor”);
}
}
class Parent extends Grandparent {
Parent() {
super(); // calls Grandparent constructor
System .out.println(“Parent constructor”);
}
}
class Child extends Parent {
Child() {
super(); // calls Parent constructor
System .out.println(“Child constructor”);
}
}
Output will be:
kotlin
CopyEdit
Grandparent constructor
Parent constructor
Child constructor
This demonstrates the order of constructor calls, starting from the topmost superclass to the subclass.
Although super is straightforward, developers sometimes make mistakes that cause compilation errors or runtime issues. Being aware of these pitfalls can improve code quality.
If a superclass lacks a no-argument constructor, every subclass constructor must explicitly call a superclass constructor using super(args). Failure to do this causes a compilation error.
super() must be the first statement in a constructor. Placing it later results in a compilation error.
Excessive use of super to access superclass fields or methods can indicate poor design. Subclasses should not rely heavily on internal implementation details of superclasses. Instead, they should interact through well-defined interfaces and methods.
super cannot be used in static methods or static blocks since these belong to the class rather than an instance.
Understanding theory is important, but seeing how super is used in real projects clarifies its importance.
Imagine you are extending a UI framework class that provides a method initialize().
java
CopyEdit
class FrameworkComponent {
void initialize() {
System. out.println(“Framework initialization”);
}
}
class CustomComponent extends FrameworkComponent {
void initialize() {
super.initialize(); // keep framework initialization
System. out.println(“Custom initialization”);
}
}
This ensures you do not lose the original framework setup while adding custom initialization.
Consider a banking system where an Account superclass requires an initial balance.
java
CopyEdit
class Account {
double balance;
Account(double initialBalance) {
balance = initialBalance;
System.out.println(“Account created with balance: ” + balance);
}
}
class SavingsAccount extends Account {
double interestRate;
SavingsAccount(double balance, double interestRate) {
super(balance); // call Account constructor
this.interestRate = interestRate;
System.out.println(“SavingsAccount created with interest rate: ” + interestRate);
}
}
Using super(balance) ensures that the Account part of the object is correctly initialized.
The use of super can contribute significantly to cleaner, more maintainable code:
While super is essential for inheritance, there are related concepts and alternatives worth mentioning.
In some cases, using composition (holding a reference to another object) rather than inheritance is preferred. Composition avoids many complexities related to super and inheritance, but has a different trade-off in terms of design.
Polymorphism allows a subclass object to be treated as an instance of its superclass. While super is about explicitly referring to the superclass’s members, polymorphism lets overridden methods be called dynamically based on the actual object type.
This refers to the current class instance and is often used to access current class members or constructors. Super is used to refer to the superclass. Knowing when to use this versus super is important for clarity.
To use super effectively:
Inheritance in Java can span multiple levels, leading to complex class hierarchies. Understanding how super works in such scenarios is crucial to writing reliable and maintainable code.
In multilevel inheritance, a subclass inherits from a superclass, which in turn inherits from another superclass. The super keyword always refers to the immediate parent class, not to the topmost ancestor.
java
CopyEdit
class Grandparent {
void greet() {
System. out.println(“Hello from Grandparent”);
}
}
class Parent extends Grandparent {
void greet() {
System .out.println(“Hello from Parent”);
}
}
class Child extends Parent {
void greet() {
super.greet(); // Calls Parent’s greet, not Grandparent’s
System out.println(“Hello from Child”);
}
}
In this example, calling super.greet() inside Child invokes the method defined in Parent, not Grandparent. To call the grandparents directly from the Child, you’d need to create a method in Parent that calls super.greet() and then invoke that method from the Child.
Java does not support multiple inheritance for classes, preventing the classic “diamond problem” where multiple paths to a common ancestor can cause ambiguity. However, Java interfaces can be multiply inherited, and super can be used with interfaces starting from Java 8 to call default methods.
java
CopyEdit
interface A {
default void show() {
System. out.println(“Interface A”);
}
}
interface B extends A {
default void show() {
System. out.println(“Interface B”);
}
}
class C implements B {
public void show() {
B.super.show(); // Call B’s default show method
}
}
This syntax, B.super.show(,,) is a special usage of super to invoke default methods from interfaces.
Understanding the difference between method overriding and variable hiding is essential when working with super.
Overriding happens when a subclass provides a new implementation for a method declared in its superclass with the same signature. The method in the subclass replaces the superclass’s version at runtime.
Using super allows the subclass to call the superclass’s version explicitly, as previously described.
Variable hiding occurs when a subclass declares a variable with the same name as one in the superclass. Unlike methods, variables are not polymorphic. Accessing variables depends on the reference type, not the actual object.
Using super.variableName accesses the variable declared in the immediate superclass, while simply using variableName accesses the subclass’s variable.
Java allows classes to have nested (inner) classes, which can also participate in inheritance. The super keyword has specialized uses here.
If an inner class extends another class and you want to access a member of the outer class’s superclass, the syntax can become complex.
java
CopyEdit
class OuterParent {
void greet() {
System out.println(“OuterParent greeting”);
}
}
class OuterChild extends OuterParent {
class InnerChild extends OuterParent {
void innerGreet() {
OuterChild.super.greet(); // Calls greet from OuterParent via OuterChild
}
}
}
This example shows how super can be qualified to refer to the superclass in the context of an enclosing classSuperer and Method Resolution Order
When a method is called using super, Java follows a strict lookup:
This predictable resolution prevents ambiguity but requires designing class hierarchies carefully.
Using super incorrectly can cause compilation errors or logical bugs. Here are common issues and how to address them.
Several object-oriented design patterns utilize super in meaningful ways.
In the Template Method pattern, a superclass defines the skeleton of an algorithm, with some steps implemented in the superclass and others left to subclasses. Subclasses override these steps and often use super to reuse default behavior.
java
CopyEdit
abstract class DataProcessor {
void process() {
readData();
processData();
writeData();
}
void readData() {
System.out.println(“Reading data”);
}
abstract void processData();
void writeData() {
System.out.println(“Writing data”);
}
}
class CustomProcessor extends DataProcessor {
void processData() {
System. out.println(“Custom processing”);
super.writeData(); // Optional reuse of superclass writeData
}
}
In the Decorator pattern, super may be used in subclasses to enhance or extend behavior by calling the original method from the superclass and then adding new functionality.
Summary of super in Object-Oriented Design
The super keyword is more than a simple reference to a superclass. It is a powerful mechanism that:
Proper understanding and disciplined use of super leads to robust, reusable, and clear Java code.
Understanding the performance impact of super is useful, especially in performance-critical applications. While super itself does not introduce significant overhead, its usage interacts with how Java handles method calls, inheritance, and constructors.
When you use super.methodName(), the JVM performs a special lookup to invoke the method from the superclass rather than the overridden version in the current class. However, this is generally just as efficient as a regular method call. Modern JVMs optimize method dispatch through techniques like inlining and just-in-time compilation, so calling a superclass method explicitly does not cause a performance penalty in most cases.
Using super() in constructors ensures that superclass constructors are called first, initializing the inherited state properly. While this adds extra constructor calls, the overhead is minimal and necessary for correct object construction. Skipping proper constructor chaining leads to unstable objects and bugs, which are far worse than the minor performance cost.
Deep inheritance hierarchies with multiple chained constructors can cause more extensive initialization and slightly longer object creation times. However, this is a design concern rather than a super keyword issue. If performance is critical, prefer composition over deep inheritance.
The concept of accessing a superclass’s members is common in object-oriented languages, but the implementation varies. Understanding these differences can clarify Java’s approach and help programmers transition between languages.
C++ supports multiple inheritance and uses the scope resolution operator (::) to explicitly call superclass methods or constructors. Unlike Java, constructors in C++ are called using an initializer list syntax. The equivalent of Java’s super is more flexible but also prone to ambiguity due to multiple inheritance.
cpp
CopyEdit
class Base {
public:
void display() {
cout << “Base display\n”;
}
};
class Derived public Base {
public:
void display() {
Base::display(); // Calls Base class method
cout << “Derived display\n”;
}
};
Python uses the super() function, but it is more dynamic. It supports cooperative multiple inheritance through the method resolution order (MRO). You often call super() without arguments in Python 3, which finds the next class in the MRO.
python
CopyEdit
class Base:
def greet(self):
print(“Hello from Base”)
class Derived(Base):
def greet(self):
super().greet() # Calls Base greet
print(“Hello from Derived”)
Python’s super() is more flexible but requires understanding the MRO, which can be complex.
C# uses the base keyword, similar to Java’s super. It accesses members of the immediate base class and calls base class constructors.
csharp
CopyEdit
class Base {
public virtual void Show() {
Console.WriteLine(“Base Show”);
}
}
class Derived Base {
public override void Show() {
base.Show(); // Calls Base Show
Console.WriteLine(“Derived Show”);
}
}
C# shares Java’s single inheritance model, so base usage closely mirrors Java’s super.
To write clean, effective Java code using super, consider these practical tips and guidelines.
When overriding methods, use super.methodName() to clearly show that the superclass implementation is being called intentionally. This improves code readability and helps other developers understand your design.
Always explicitly call superclass constructors with super(args) when the superclass has parameterized constructors. This prevents hidden bugs related to uninitialized superclass state.
Overuse of super may indicate improper class design. If you find yourself frequently accessing superclass members, consider refactoring to reduce tight coupling, such as using interfaces or composition.
In complex inheritance hierarchies, document why and where you use super. This is especially useful when calling superclass methods to extend behavior rather than replace it.
When you override methods and call super, test the combined behavior to ensure the superclass logic and subclass extensions interact correctly.
Here are some frequent programming situations involving super and how to handle them effectively.
When overriding toString(), it is common to call super.toString() to include the superclass’s string representation.
java
CopyEdit
class Person {
String name;
Person(String name) {
this.name = name;
}
public String toString() {
return “Person: ” + name;
}
}
class Employee extends Person {
int employeeId;
Employee(String name, int id) {
super(name);
this.employeeId = id;
}
public String toString() {
return super.toString() + “, Employee ID: ” + employeeId;
}
}
This combines the string representations.
If a subclass needs to add logging but preserve the original method logic, use super.
java
CopyEdit
class Logger {
void log(String message) {
System. out.println(“Log: ” + message);
}
}
class TimestampLogger extends Logger {
void log(String message) {
System.out.print(System.currentTimeMillis() + ” “);
super.log(message);
}
}
If both classes have a variable with the same name, use super to specify which variable to access.
java
CopyEdit
class Vehicle {
int speed = 50;
}
class Car extends Vehicle {
int speed = 100;
void printSpeeds() {
System.out.println(“Car speed: ” + speed);
System.out.println(“Vehicle speed: ” + super.speed);
}
}
When planning inheritance, keep the super keyword’s behavior in mind to avoid confusion and errors.
Deep inheritance hierarchies often complicate code and increase reliance on super. Using composition where one class holds instances of other classes can lead to more flexible and maintainable designs.
Only override superclass methods when necessary. If many methods need overriding, consider whether inheritance is the best approach.
Ensure constructors in inheritance hierarchies properly call super() with relevant parameters. This avoids runtime exceptions and ensures fully initialized objects.
The super keyword is an essential part of Java’s inheritance model. It provides a direct way to access superclass methods, fields, and constructors, enabling robust object-oriented designs.
Key takeaways include:
Mastering super is fundamental for any Java programmer aiming to write clean, efficient, and scalable object-oriented applications.
Popular posts
Recent Posts