super Keyword
The super keyword refers to the superclass part of the current object. A subclass uses it in two ways: super(...) calls a superclass constructor, and super.member reaches a superclass method or field, most often to call the parent's version of a method the subclass has overridden. super is what lets a subclass build on its parent rather than replacing it.
Calling a Superclass Constructor: super(...)
When an object is created, every class in its hierarchy initializes its own part, starting from the top. A subclass constructor chooses which parent constructor runs by calling super(...):
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
if (age < 0) throw new IllegalArgumentException("age cannot be negative");
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public class Student extends Person {
private final String rollNumber;
public Student(String name, int age, String rollNumber) {
super(name, age); // initialize the Person part
this.rollNumber = rollNumber; // then the Student part
}
}
Person's fields are private, so Student cannot assign them directly. It passes the values to Person's constructor, which also applies Person's validation. Every Student is therefore guaranteed to be a valid Person.
The implicit super()
If a constructor does not call super(...) or this(...), the compiler inserts super(), a call to the parent's no-argument constructor:
public class Animal {
public Animal() { System.out.println("Animal constructed"); }
}
public class Dog extends Animal {
public Dog() {
// compiler inserts: super();
System.out.println("Dog constructed");
}
}
new Dog();
Output:
Animal constructed
Dog constructed
If the parent has no no-argument constructor, the inserted super() fails to compile:
public class Student extends Person {
public Student(String rollNumber) {
// implicit super() → error: constructor Person in class Person cannot be applied to given types
}
}
The fix is an explicit super(...) with the arguments the parent requires.
Rules for super(...)
- It can appear only in a constructor.
- A constructor may call
super(...)orthis(...), but not both. Whenthis(...)delegates to another constructor, that constructor is the one that callssuper(...). - Up to Java 24, it must be the first statement. From Java 25 (flexible constructor bodies), statements may come before it, provided they do not use the object being constructed. This is useful for validating or transforming arguments first.
// Works on all versions: compute the argument with a static helper
public Student(String name, int age, String rollNumber) {
super(normalize(name), age);
this.rollNumber = rollNumber;
}
private static String normalize(String name) {
return name == null ? "" : name.strip();
}
Construction Order Across a Hierarchy
class A {
A() { System.out.println("A"); }
}
class B extends A {
B() { System.out.println("B"); }
}
class C extends B {
C() { System.out.println("C"); }
}
new C();
Output:
A
B
C
C() calls B(), which calls A(), which calls Object(). The bodies then complete in order from the top down. By the time a subclass constructor body runs, all of its ancestors are fully initialized.
Calling an Overridden Method: super.method()
When a subclass overrides a method, a plain call runs the subclass version. super.method() runs the parent's version instead, which lets the override extend the original behavior rather than duplicate it:
public class Employee {
protected final double baseSalary;
public Employee(double baseSalary) { this.baseSalary = baseSalary; }
public double monthlyPay() {
return baseSalary / 12;
}
public String describe() {
return "Employee earning " + String.format("%.2f", monthlyPay()) + " per month";
}
}
public class SalesEmployee extends Employee {
private final double commission;
public SalesEmployee(double baseSalary, double commission) {
super(baseSalary);
this.commission = commission;
}
@Override
public double monthlyPay() {
return super.monthlyPay() + commission; // parent's calculation plus extra
}
@Override
public String describe() {
return super.describe() + " (includes commission " + commission + ")";
}
}
Employee e = new SalesEmployee(600_000, 8_000);
System.out.println(e.monthlyPay());
System.out.println(e.describe());
Output:
58000.0
Employee earning 58000.00 per month (includes commission 8000.0)
Trace e.describe():
SalesEmployee.describe()runs and callssuper.describe().Employee.describe()runs and callsmonthlyPay(). Because the object is aSalesEmployee, this dispatches toSalesEmployee.monthlyPay(), notEmployee's.SalesEmployee.monthlyPay()callssuper.monthlyPay(), giving 50,000, and adds 8,000.- The result flows back up, and
SalesEmployee.describe()appends its note.
super.method() selects the parent's implementation for that single call. Calls made from inside the parent's method are still dispatched polymorphically. This is often exactly what you want, but it is important to understand when reading such code.
Without super: accidental recursion
Forgetting super. in an override calls the override itself:
@Override
public double monthlyPay() {
return monthlyPay() + commission; // calls itself forever → StackOverflowError
}
super reaches only one level
super.method() refers to the nearest superclass's version, which may itself be inherited from higher up. There is no super.super.method(); a class cannot skip its parent to reach its grandparent. If such access seems necessary, the hierarchy usually needs redesigning.
Accessing Superclass Fields: super.field
If a subclass declares a field with the same name as a field in its parent, the subclass field hides the parent's. super.field reaches the hidden one:
class Parent {
protected String label = "parent";
}
class Child extends Parent {
protected String label = "child";
void show() {
System.out.println(label); // child
System.out.println(this.label); // child
System.out.println(super.label); // parent
}
}
The object now contains two separate label fields, which is confusing and rarely intended. Avoid declaring fields with the same names as inherited ones; this use of super mostly appears when reading other people's code.
super in Interfaces with Default Methods
When a class implements two interfaces that both provide a default method with the same signature, it must override the method, and can call a specific interface's version with InterfaceName.super.method():
interface Camera {
default String status() { return "camera ready"; }
}
interface Microphone {
default String status() { return "mic ready"; }
}
class Webcam implements Camera, Microphone {
@Override
public String status() {
return Camera.super.status() + ", " + Microphone.super.status();
}
}
System.out.println(new Webcam().status()); // camera ready, mic ready
Without the override, the class would not compile, because Java cannot choose between the two defaults on its own.
super Is Not Available in Static Context
Like this, super refers to the current object, so it cannot be used in static methods:
public static void helper() {
super.toString(); // compile error: non-static variable super cannot be referenced from a static context
}
this Versus super
this | super | |
|---|---|---|
| Refers to | The current object, viewed as its own class | The current object, viewed as its superclass |
| Constructor call | this(...): another constructor of the same class | super(...): a constructor of the parent class |
| Method call | this.m(): dynamic dispatch, may run an override | super.m(): the parent's version specifically |
| Field access | this.f: this class's field | super.f: the parent's (hidden) field |
Practical Example: Logging Shapes
import java.util.List;
public class ShapesDemo {
static abstract class Shape {
private final String color;
Shape(String color) {
this.color = color;
}
abstract double area();
@Override
public String toString() {
return String.format("%s %s with area %.2f",
color, getClass().getSimpleName().toLowerCase(), area());
}
}
static class Rectangle extends Shape {
protected final double width, height;
Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
double area() { return width * height; }
}
static class Square extends Rectangle {
Square(String color, double side) {
super(color, side, side); // reuse Rectangle's constructor
}
@Override
public String toString() {
return super.toString() + " (side " + width + ")"; // extend parent's text
}
}
static class Circle extends Shape {
private final double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
double area() { return Math.PI * radius * radius; }
}
public static void main(String[] args) {
List<Shape> shapes = List.of(
new Rectangle("blue", 4, 2.5),
new Square("red", 3),
new Circle("green", 1.5)
);
shapes.forEach(System.out::println);
}
}
Output:
blue rectangle with area 10.00
red square with area 9.00 (side 3.0)
green circle with area 7.07
Every constructor passes the color up to Shape with super(color), so color handling exists in one place. Square reuses all of Rectangle's setup by calling super(color, side, side) and inherits its area() unchanged. Square.toString() calls super.toString(), which is Shape's version (inherited by Rectangle), and appends the side length. Inside that inherited toString, getClass() and area() still resolve to the actual object, which is why it prints "square" and the square's area.