← thecodex.expert · The Codex Family of Knowledge
Java

Inheritance and Polymorphism

A variable declared as the parent type, but holding a child object, calls the CHILD’s overridden method — the reference’s declared type has nothing to do with which version actually runs.

Java 21 LTS Resolved by runtime type Last verified:
Canonical Definition

extends establishes single inheritance: a subclass automatically has every non-private field and method of its superclass, plus whatever it adds or overrides itself. Polymorphism is the resulting behavior at a method call site: when a subclass overrides a method, calling that method through a superclass-typed reference still invokes the subclass's version, because Java resolves overridden methods based on the object's actual runtime type — dynamic dispatch — not the declared type of the variable holding the reference.

extends: single inheritance

Dog automatically has eat() without redeclaring it, and provides its own version of makeSound() — super() explicitly calls the parent constructor. Whenever the parent has no no-argument constructor, the subclass must supply that call itself rather than relying on the compiler-inserted default; through Java 24, it also had to be the very first statement in the constructor body. Java 25's JEP 513 (Flexible Constructor Bodies) relaxed that specific rule: statements that don't touch this — validating an argument and throwing before construction even starts, for instance — can now come before the super()/this() call. The call itself is still required when there's no no-argument parent constructor to fall back on; only its "must be literally the first line" restriction is gone.

JavaAnimal.java
public class Animal {
    protected String name;

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

    public void eat() { System.out.println(name + " is eating"); }
    public void makeSound() { System.out.println("..."); }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);   // must call the parent's constructor explicitly
    }

    @Override
    public void makeSound() {   // overrides the parent's version
        System.out.println(name + " says Woof!");
    }
}

Polymorphism: resolved by the ACTUAL object, not the reference type

Even though pet is declared as Animal, calling makeSound() runs Dog's version — the JVM looks at what pet actually points to at runtime, ignoring the declared reference type entirely for this decision.

JavaPolymorphism.java
Animal pet = new Dog("Rex");   // declared type: Animal, actual type: Dog
pet.makeSound();                 // prints "Rex says Woof!" — Dog's override runs

Animal[] animals = { new Dog("Rex"), new Cat("Whiskers") };
for (Animal a : animals) {
    a.makeSound();   // each one calls ITS OWN override, through the same Animal-typed variable
}

super: accessing the parent's version explicitly

super.makeSound() inside the override calls the parent's original implementation, letting a subclass extend rather than completely replace inherited behavior.

JavaSuperCall.java
public class Puppy extends Dog {
    public Puppy(String name) {
        super(name);   // Dog has no no-argument constructor either, so this is required here too
    }

    @Override
    public void makeSound() {
        super.makeSound();   // calls Dog's makeSound() first
        System.out.println("(but it's more of a yip)");
    }
}

Sources

1
Oracle. The Java Tutorials, "Overriding and Hiding Methods" and "Polymorphism," docs.oracle.com/javase/tutorial/java/IandI.