☕ Java

super Keyword

The super keyword in Java is a reference to the superclass portion of the current object. It allows a subclass to explicitly access superclass members — fields, methods, and constructors — that would otherwise be hidden or replaced by the subclass's own declarations. super is the bridge between a subclass and its parent class, enabling subclasses to extend rather than completely replace superclass behaviour, and ensuring that the superclass is properly initialised before the subclass adds its own initialisation.

What super Is and When It Is Needed

When a subclass defines a method with the same name and signature as a method in its superclass, the superclass's method is hidden by the override. Inside the subclass, any unqualified call to that method name will invoke the subclass's version. This is exactly what runtime polymorphism requires. But sometimes the subclass needs to invoke the superclass's version of the method — to extend its behaviour rather than completely replace it. The super keyword provides access to the superclass's version. Similarly, when a subclass declares a field with the same name as a superclass field, the subclass field hides the superclass field. super provides access to the superclass field when the same name exists at both levels of the hierarchy. The most critical use of super is in constructors. Java requires that the very first statement of every subclass constructor either explicitly calls a superclass constructor with super(...) or another constructor in the same class with this(...). If neither appears, the compiler inserts an implicit super() call — a no-argument call to the superclass constructor. If the superclass has no no-argument constructor, the implicit call fails and the code does not compile. This requirement exists because the superclass portion of the object must be initialised before the subclass can add its own initialisation — the object is built from the top of the hierarchy downward, and each level is responsible for initialising its own portion. Understanding super requires understanding Java's object model: when you create a Dog object, the Dog object contains within it all the fields of Animal (its superclass) and all the fields of Object (Animal's superclass). The super keyword is the mechanism for reaching into those parent-class portions of the same object.
Java
// ── super accesses the superclass portion of the current object: ──────
public class Animal {
    private String name;
    private String species;

    public Animal(String name, String species) {
        this.name    = name;
        this.species = species;
    }

    public String getName()    { return name;    }
    public String getSpecies() { return species; }

    public String describe() {
        return name + " is a " + species;
    }
}

public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name, "Canis lupus familiaris");  // initialise Animal portion
        this.breed = breed;
    }

    public String getBreed() { return breed; }

    @Override
    public String describe() {
        // Call superclass version, then add breed information:
        return super.describe() + " (" + breed + ")";
    }
}

Dog rex = new Dog("Rex", "German Shepherd");
System.out.println(rex.describe());
// Rex is a Canis lupus familiaris (German Shepherd)
//  ↑ from super.describe()          ↑ from this class

// ── Without super — completely replaces parent behaviour: ─────────────
public class CatWithoutSuper extends Animal {
    private String furPattern;

    public CatWithoutSuper(String name, String furPattern) {
        super(name, "Felis catus");
        this.furPattern = furPattern;
    }

    @Override
    public String describe() {
        // Does NOT call super — Animal's describe() is completely replaced:
        return getName() + " has " + furPattern + " fur";
    }
}

CatWithoutSuper whiskers = new CatWithoutSuper("Whiskers", "tabby");
System.out.println(whiskers.describe());
// Whiskers has tabby fur   ← no species information

super() — Calling the Superclass Constructor

The super() call invokes a superclass constructor from within a subclass constructor. It must always be the very first statement in the constructor body — the compiler enforces this with no exceptions. If any code appears before super(), it is a compile error. This strict ordering ensures the superclass portion of the object is fully initialised before the subclass constructor runs, because the subclass constructor's code might read superclass fields and those fields must be initialised first. When no explicit super() call is present, Java implicitly inserts super() as the first statement. This implicit call succeeds only if the superclass has a no-argument constructor. If the superclass only has parameterised constructors, the implicit call fails and the compiler demands an explicit super(...) with appropriate arguments. The super() call and this() call (calling another constructor in the same class) are mutually exclusive in a constructor — you can have one or the other as the first statement but never both. This makes sense logically: if a constructor delegates to another constructor in the same class with this(), that constructor will eventually call super() on its way up the hierarchy. The super() call cannot therefore appear again in the original constructor. The super() call can pass computed values, but only from expressions that do not involve the current object (no this reference before super() completes). This is the same reasoning as the this-escape problem: the object is not yet constructed, so calling instance methods to compute super() arguments would access partially initialised state.
Java
// ── super() must be first — enforces top-down initialisation: ─────────
public class Vehicle {
    private final String make;
    private final String model;
    private final int    year;

    public Vehicle(String make, String model, int year) {
        if (year < 1886 || year > 2100)
            throw new IllegalArgumentException("Invalid year: " + year);
        this.make  = make;
        this.model = model;
        this.year  = year;
        System.out.println("Vehicle initialised: " + make + " " + model);
    }

    public String getMake()  { return make;  }
    public String getModel() { return model; }
    public int    getYear()  { return year;  }
}

public class ElectricVehicle extends Vehicle {
    private final int batteryCapacityKwh;
    private final int rangeKm;

    public ElectricVehicle(String make, String model, int year,
                            int batteryKwh, int rangeKm) {
        super(make, model, year);   // MUST be first — initialises Vehicle portion
        // If super() were not first, batteryCapacityKwh would be accessed
        // before the Vehicle portion (make, model, year) is initialised.
        this.batteryCapacityKwh = batteryKwh;
        this.rangeKm            = rangeKm;
        System.out.println("EV initialised: " + batteryKwh + "kWh, " + rangeKm + "km range");
    }
}

public class LuxuryEV extends ElectricVehicle {
    private final String interiorMaterial;

    public LuxuryEV(String make, String model, int year,
                     int batteryKwh, int rangeKm, String interior) {
        super(make, model, year, batteryKwh, rangeKm);  // calls ElectricVehicle(...)
        this.interiorMaterial = interior;
        System.out.println("LuxuryEV initialised: " + interior + " interior");
    }
}

LuxuryEV car = new LuxuryEV("Tesla", "Model S", 2024, 100, 650, "Leather");
// Output shows top-down order:
// Vehicle initialised: Tesla Model S
// EV initialised: 100kWh, 650km range
// LuxuryEV initialised: Leather interior

// ── Implicit super() — when superclass has no-arg constructor: ────────
public class Base {
    public Base() {                     // no-arg constructor
        System.out.println("Base()");
    }
}

public class Derived extends Base {
    public Derived() {
        // super() inserted automatically by compiler
        System.out.println("Derived()");
    }
}

new Derived();
// Base()      ← implicit super() called first
// Derived()

// ── When implicit super() fails — must be explicit: ───────────────────
public class NoDefaultBase {
    public NoDefaultBase(String required) {
        System.out.println("NoDefaultBase: " + required);
    }
    // No no-arg constructor — implicit super() cannot be used.
}

public class ChildOfNoDefault extends NoDefaultBase {
    public ChildOfNoDefault() {
        // super();         // COMPILE ERROR — NoDefaultBase() does not exist
        super("provided");  // ✓ must explicitly call the parameterised constructor
    }
}

super.method() — Calling the Superclass Method

When a subclass overrides a method, the superclass's version of that method is no longer accessible by its simple name inside the subclass — the overriding version is called instead. The super.methodName() syntax explicitly routes the call to the superclass's version, bypassing the override. The most important and common use is the extend pattern: the subclass's overriding method calls super.method() to perform the parent's logic, and then adds its own behaviour on top. This avoids code duplication — the common logic lives in the superclass and is reused, while each subclass adds its specific extension. This is fundamentally different from completely replacing the superclass's behaviour, where super is not called at all. The extend pattern is the basis of the template method design pattern. A superclass defines the skeleton of an algorithm and calls abstract or overridable methods at specific points. Subclasses fill in those points by overriding the hook methods. The superclass's template method should be called by client code, and the subclass hooks are called from within it — the subclass never needs to call the template method itself. super.method() can only be used to call the immediate superclass's version, not any further ancestor. You cannot write super.super.method() to skip two levels. If you need the grandparent's version, the parent class must provide a way to call it — typically by calling super.method() in its own implementation and having the grandchild rely on normal polymorphism to reach it.
Java
// ── Extend pattern — super call + subclass addition: ─────────────────
public class Logger {
    private String serviceName;

    public Logger(String serviceName) {
        this.serviceName = serviceName;
    }

    public void log(String level, String message) {
        System.out.printf("[%s] [%s] %s%n",
            java.time.LocalTime.now(), level, message);
    }

    public void info(String message)  { log("INFO",  message); }
    public void warn(String message)  { log("WARN",  message); }
    public void error(String message) { log("ERROR", message); }
}

public class ServiceLogger extends Logger {
    private final String serviceName;
    private int logCount = 0;

    public ServiceLogger(String serviceName) {
        super(serviceName);
        this.serviceName = serviceName;
    }

    @Override
    public void log(String level, String message) {
        logCount++;
        // Extend: prefix service name, then call parent logging:
        super.log(level, "[" + serviceName + "] " + message);
    }

    public int getLogCount() { return logCount; }
}

public class AlertingLogger extends ServiceLogger {
    private final List<String> alerts = new ArrayList<>();

    public AlertingLogger(String serviceName) {
        super(serviceName);
    }

    @Override
    public void error(String message) {
        // Extend: call parent's error() (which chains up to Logger.log()),
        // then additionally record the alert:
        super.error(message);       // calls ServiceLogger → Logger
        alerts.add(message);        // additional alerting behaviour
        System.out.println("ALERT SENT: " + message);
    }

    public List<String> getAlerts() { return new ArrayList<>(alerts); }
}

AlertingLogger logger = new AlertingLogger("OrderService");
logger.info("Order created");       // goes through ServiceLogger → Logger
logger.error("Payment failed");     // goes through all three levels

// info  output: [12:00:00] [INFO]  [OrderService] Order created
// error output: [12:00:01] [ERROR] [OrderService] Payment failed
//              ALERT SENT: Payment failed
System.out.println("Total log entries: " + logger.getLogCount());  // 2

// ── Accessing hidden superclass fields with super: ────────────────────
public class Employee {
    protected String name = "Employee";     // field in superclass
}

public class Manager extends Employee {
    private String name = "Manager";        // hides Employee.name

    public void printNames() {
        System.out.println("this.name:  " + this.name);   // Manager
        System.out.println("super.name: " + super.name);  // Employee
    }
}

new Manager().printNames();
// this.name:  Manager
// super.name: Employee

super vs this — Comparison and Interaction

Both super and this are references to the current object — they both refer to the same object in memory. The difference is which portion of the object's type hierarchy they access. this accesses the current class's members (and inherited members not shadowed by the current class). super accesses the immediate superclass's version of a member, even when the current class has overridden or hidden it. The two are mutually exclusive as the first statement in a constructor: a constructor can start with this(...) to delegate to another constructor in the same class, or with super(...) to call the superclass constructor, but never both. When this(...) is used, it will eventually call super(...) as it chains through the constructors, so the superclass is always initialised — just through the chaining path rather than directly. In any other position (outside the first statement), super.method() and this.method() can both appear in the same method body. It is also legal to have a class where one constructor uses this(...) to chain to another constructor that then uses super(...). Understanding this interaction is important for reading class hierarchies that use both forms of constructor delegation.
Java
// ── this vs super — both refer to the same object: ───────────────────
public class Base {
    protected int value = 10;

    public int getValue() { return value; }
    public String type()  { return "Base"; }
}

public class Child extends Base {
    protected int value = 20;    // hides Base.value

    @Override
    public String type() { return "Child"; }

    public void compare() {
        System.out.println("this.value:    " + this.value);    // 20 (Child's)
        System.out.println("super.value:   " + super.value);   // 10 (Base's)
        System.out.println("this.type():   " + this.type());   // Child
        System.out.println("super.type():  " + super.type());  // Base
        System.out.println("this == same object: " + (this != null));  // true
        // this and super both refer to the same object —
        // super just gives access to a different portion of it.
    }
}

new Child().compare();

// ── Constructor delegation — this() eventually calls super(): ──────────
public class Product {
    private final String name;
    private final double price;
    private final String sku;

    // Canonical constructor — calls super (Object):
    public Product(String name, double price, String sku) {
        // super() implicitly inserted here — calls Object()
        this.name  = name;
        this.price = price;
        this.sku   = sku;
    }

    // Convenience constructors use this() to delegate:
    public Product(String name, double price) {
        this(name, price, generateSku(name));   // delegates to canonical
    }

    public Product(String name) {
        this(name, 0.0);                         // delegates to 2-param
    }

    private static String generateSku(String name) {
        return name.toUpperCase().replaceAll("\s+", "-");
    }
}

// ── super() and this() are mutually exclusive as first statement: ──────
public class ExtendedProduct extends Product {

    private final String category;

    public ExtendedProduct(String name, double price, String category) {
        super(name, price);   // ✓ super() as first statement
        this.category = category;
    }

    public ExtendedProduct(String name, String category) {
        this(name, 0.0, category);   // ✓ this() as first statement
        // super(name);              // ✗ COMPILE ERROR — cannot have both
    }
}

Related Topics in Object-Oriented Programming

OOP Concepts
Object-Oriented Programming (OOP) is a programming paradigm that organises software around objects — self-contained units that combine data (fields) and behaviour (methods). Java is a class-based, object-oriented language where almost everything is an object. OOP provides four foundational principles: encapsulation, inheritance, polymorphism, and abstraction. Together they produce software that is modular, reusable, maintainable, and easier to reason about as systems grow in complexity.
Classes
A class is the fundamental building block of Java. It is a blueprint that defines the structure and behaviour of objects — what data each object holds (fields) and what operations it can perform (methods). Every Java program is composed of classes. Understanding how to design a class well — choosing the right access modifiers, separating state from behaviour, and writing cohesive single-responsibility classes — is the foundation of object-oriented programming in Java. This entry covers class anatomy, fields, methods, access modifiers, static vs instance members, the this keyword, and class design principles.
Objects
An object is a runtime instance of a class. Where a class is a blueprint that exists in source code, an object is a living entity that exists in memory during program execution — it has its own identity, its own state stored in its fields, and the ability to respond to method calls. Every object has three fundamental properties: identity (a unique memory address), state (the current values of its fields), and behaviour (the methods it responds to). This entry covers object identity vs equality, the Object class hierarchy, object state and mutation, method calls, toString, equals and hashCode, and the object lifecycle.
Object Creation
Object creation in Java is the process of allocating memory, initialising fields, and running constructor logic to bring an object into existence. The new keyword is the primary mechanism, but Java also provides factory methods, builder patterns, copy constructors, and object cloning. Constructors are special methods that set up the initial state — their design determines how easy or difficult the class is to use correctly. This entry covers constructors in depth, constructor overloading and chaining, copy constructors, factory methods, the builder pattern, and the difference between shallow and deep copy.