Verified for the 2025 AP Computer Science A exam•Last Updated on June 18, 2024
When working with inheritance in Java, subclasses often need to interact with their parent classes. Perhaps you need to call a constructor from the superclass, or maybe you want to use a method from the superclass that you've overridden. This is where the super keyword comes into play. The super keyword allows subclasses to access and use constructors and methods from their superclass, creating a bridge between parent and child classes. This guide explores how to use the super keyword effectively, its various applications, and why it's an essential tool in inheritance relationships.
The super keyword in Java is a reference variable that refers to the immediate parent class object. It provides a way for a subclass to access its superclass's constructors and methods. Think of it as a way for a child class to "reach up" and use functionality from its parent class.
The super keyword has two main uses:
When you create an object of a subclass, Java must first initialize the superclass portion of the object. This is done by calling a constructor from the superclass.
You can explicitly call a superclass constructor using the super keyword followed by parentheses and any necessary arguments:
super(); // Call the no-argument constructor of the superclass super(arg1, arg2); // Call a parameterized constructor of the superclass
Important rules about using super for constructors:
Here's an example of using super to call a superclass constructor:
// Superclass public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // Getters and other methods... } // Subclass public class Student extends Person { private String studentId; public Student(String name, int age, String studentId) { super(name, age); // Call Person constructor with name and age this.studentId = studentId; } // Other methods... }
In this example, the Student
constructor uses super(name, age)
to call the Person
constructor, ensuring that the name
and age
fields inherited from the Person
class are properly initialized.
The super keyword can also be used to call methods from the superclass. This is particularly useful when you've overridden a method but still want to use the original implementation as part of your new implementation.
To call a superclass method, use the super keyword followed by a dot and the method name:
super.methodName(); // Call the superclass version of the method
Here's an example of using super to call a superclass method:
// Superclass public class Animal { public void makeSound() { System.out.println("The animal makes a generic sound"); } } // Subclass public class Dog extends Animal { @Override public void makeSound() { super.makeSound(); // Call Animal's makeSound method System.out.println("The dog barks: Woof! Woof!"); } }
When dog.makeSound()
is called, it first executes the makeSound()
method from the Animal
class, then executes the additional code in the Dog
class's version.
Output:
The animal makes a generic sound The dog barks: Woof! Woof!
When calling a superclass constructor or method with super, you must pass appropriate parameters that match the parameter list of the superclass constructor or method you're calling.
For constructors:
// Superclass with multiple constructors public class Vehicle { private String make; private String model; public Vehicle() { this.make = "Unknown"; this.model = "Unknown"; } public Vehicle(String make, String model) { this.make = make; this.model = model; } } // Subclass calling different superclass constructors public class Car extends Vehicle { private int numDoors; public Car(int numDoors) { super(); // Calls Vehicle's no-argument constructor this.numDoors = numDoors; } public Car(String make, String model, int numDoors) { super(make, model); // Calls Vehicle's two-argument constructor this.numDoors = numDoors; } }
For methods:
// Superclass public class Shape { public double calculateArea() { return 0.0; } public void display(String prefix) { System.out.println(prefix + ": This is a shape"); } } // Subclass public class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } @Override public void display(String prefix) { super.display(prefix); // Calls Shape's display method with the prefix parameter System.out.println("This is a circle with radius " + radius); } }
Use super when you want to extend rather than completely replace the behavior of a superclass method:
public class Employee { public void displayInfo() { System.out.println("Employee Information"); } } public class Manager extends Employee { @Override public void displayInfo() { super.displayInfo(); // Call the original method first System.out.println("This employee is a manager"); } }
Use super when you need to ensure proper initialization of inherited fields:
public class BankAccount { protected double balance; public BankAccount(double initialBalance) { this.balance = initialBalance; } } public class SavingsAccount extends BankAccount { private double interestRate; public SavingsAccount(double initialBalance, double interestRate) { super(initialBalance); // Initialize the balance through the superclass constructor this.interestRate = interestRate; } }
Use super when there might be naming conflicts between superclass and subclass members:
public class Parent { protected int value = 10; public void printValue() { System.out.println("Parent value: " + value); } } public class Child extends Parent { private int value = 20; // Same name as the superclass field @Override public void printValue() { System.out.println("Child value: " + value); // Refers to Child's value (20) System.out.println("Parent value: " + super.value); // Refers to Parent's value (10) super.printValue(); // Calls Parent's printValue method } }
Forgetting that super() must be the first statement in a constructor:
// WRONG public Child(int value) { this.value = value; super(); // Error: super() must be first statement }
Calling a non-existent superclass constructor:
// WRONG - if Parent doesn't have a two-argument constructor public Child(int a, int b) { super(a, b); // Error: no matching constructor in Parent }
Trying to use super in a class that doesn't extend another class:
// WRONG public class Standalone { public void someMethod() { super.someMethod(); // Error: Standalone doesn't extend anything } }
Always call the most appropriate superclass constructor to properly initialize inherited state.
Use super.method() when extending functionality rather than completely replacing it.
Make clear which method you're calling by using super explicitly when overriding methods.
Document constructor chains to help other developers understand initialization sequences.
// Base class public class Device { private String brand; public Device(String brand) { this.brand = brand; System.out.println("Device constructor called"); } public void turnOn() { System.out.println("Device turning on"); } public String getBrand() { return brand; } } // Intermediate class public class Phone extends Device { private String model; public Phone(String brand, String model) { super(brand); // Call Device constructor this.model = model; System.out.println("Phone constructor called"); } @Override public void turnOn() { super.turnOn(); // Call Device's turnOn method System.out.println("Phone display lighting up"); } public void makeCall(String number) { System.out.println("Calling " + number); } public String getModel() { return model; } } // Leaf class public class Smartphone extends Phone { private String operatingSystem; public Smartphone(String brand, String model, String operatingSystem) { super(brand, model); // Call Phone constructor this.operatingSystem = operatingSystem; System.out.println("Smartphone constructor called"); } @Override public void turnOn() { super.turnOn(); // Call Phone's turnOn method System.out.println("Smartphone showing boot animation"); } public void installApp(String appName) { System.out.println("Installing " + appName); } public void displayInfo() { System.out.println("Brand: " + super.getBrand()); // Call superclass method System.out.println("Model: " + super.getModel()); // Call superclass method System.out.println("OS: " + operatingSystem); } } // Usage public class Main { public static void main(String[] args) { Smartphone myPhone = new Smartphone("Samsung", "Galaxy", "Android"); // Constructor chain output: // Device constructor called // Phone constructor called // Smartphone constructor called myPhone.turnOn(); // Output: // Device turning on // Phone display lighting up // Smartphone showing boot animation myPhone.displayInfo(); // Output: // Brand: Samsung // Model: Galaxy // OS: Android } }
// Base class public class Account { protected double balance; protected double interestRate; public Account(double initialBalance, double interestRate) { this.balance = initialBalance; this.interestRate = interestRate; } public void deposit(double amount) { if (amount > 0) { balance += amount; System.out.println("Deposited: $" + amount); System.out.println("New balance: $" + balance); } } public void withdraw(double amount) { if (amount > 0 && balance >= amount) { balance -= amount; System.out.println("Withdrawn: $" + amount); System.out.println("New balance: $" + balance); } else { System.out.println("Insufficient funds"); } } public void addInterest() { double interest = balance * interestRate; balance += interest; System.out.println("Interest added: $" + interest); System.out.println("New balance: $" + balance); } } // Subclass public class CheckingAccount extends Account { private double overdraftLimit; public CheckingAccount(double initialBalance, double interestRate, double overdraftLimit) { super(initialBalance, interestRate); // Call Account constructor this.overdraftLimit = overdraftLimit; } @Override public void withdraw(double amount) { if (amount > 0) { // Can withdraw if within combined balance and overdraft limit if (balance + overdraftLimit >= amount) { // Use the standard withdraw logic when possible if (balance >= amount) { super.withdraw(amount); // Use superclass method for normal case } else { // Handle overdraft case ourselves balance -= amount; System.out.println("Withdrawn: $" + amount + " (includes overdraft)"); System.out.println("New balance: $" + balance); } } else { System.out.println("Exceeds overdraft limit"); } } } @Override public void addInterest() { // Checking accounts have lower interest, only apply if balance is positive if (balance > 0) { super.addInterest(); // Use superclass method } } }
The super keyword is a powerful tool in Java inheritance, allowing subclasses to interact with their superclasses in meaningful ways. By using super to call constructors, you ensure proper initialization of inherited fields. By using super to call methods, you can extend rather than completely replace superclass behavior. Remember that super can only access the immediate parent class, and it must be used as the first statement in constructors. When used properly, the super keyword helps create well-designed inheritance hierarchies where subclasses can leverage and build upon the functionality provided by their superclasses.