Inheritance and Polymorphism

Learning goals

Extend classes with inheritance, override methods correctly, use polymorphism at runtime, and recognize when composition is the better design choice.


extends keyword

public class Animal {
    protected String name;

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

    public void speak() {
        System.out.println(name + " makes a sound");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);  // must call superclass constructor first
    }

    @Override
    public void speak() {
        System.out.println(name + " barks");
    }
}

Dog is-a Animal and inherits accessible fields and methods.


super

@Override
public void speak() {
    super.speak();
    System.out.println("Louder!");
}

super(...) in constructor chains to parent constructor. super.method() invokes overridden parent behavior.


Polymorphism

Animal a = new Dog("Rex");
a.speak();  // "Rex barks" — runtime type decides

Reference type (Animal) limits compile-time API; runtime type (Dog) selects override implementation (dynamic dispatch).

void feed(Animal animal) {
    animal.speak();  // works for Dog, Cat, ...
}

@Override

Always annotate overrides—compiler catches signature mistakes (common with interface default methods):

@Override
public String toString() {
    return "Dog(" + name + ")";
}

If parent method disappears, override fails at compile time instead of silently becoming overload.


Up casting and downcasting

Animal a = new Dog("Rex");
Dog d = (Dog) a;           // explicit downcast — OK here

Animal unknown = new Animal("X");
// Dog bad = (Dog) unknown;  // ClassCastException at runtime

Prefer instanceof pattern (JDK 16+):

if (a instanceof Dog dog) {
    dog.fetch();  // dog in scope
}

Method hiding (static) vs overriding (instance)

class Parent {
    static void hello() { System.out.println("parent"); }
}
class Child extends Parent {
    static void hello() { System.out.println("child"); }
}

Parent p = new Child();
p.hello();              // "parent" — static, resolved by reference type
Child c = new Child();
c.hello();              // "child"

Instance methods override; static methods hide—avoid inheriting static confusion; call via class name.


Composition vs inheritance

Favor composition when relationship is “has-a” or behavior mixing:

public class EmailNotifier implements Notifier {
    private final MailClient client;

    public EmailNotifier(MailClient client) {
        this.client = client;
    }
    // delegate to client
}

Deep inheritance hierarchies become rigid. “Prefer composition over inheritance” is standard interview guidance.


Object root class

Every class extends java.lang.Object implicitly:

@Override
public boolean equals(Object o) { ... }

@Override
public int hashCode() { ... }

@Override
public String toString() { ... }

Dedicated chapter covers equals/hashCode in depth.


sealed classes (JDK 17+, awareness)

Restrict who can extend:

public sealed class Shape permits Circle, Rectangle { }
public final class Circle extends Shape { }

Useful for exhaustive switches and domain modeling.


Common mistakes

  1. Forgetting super() when parent has no default constructor.
  2. Overriding with weaker access — cannot reduce visibility (public → package-private).
  3. Calling overridable method from constructor — subclass override may run on partially constructed object.
  4. Deep inheritance for code reuse — leads to fragile base class; extract shared behavior to helper/composition.
  5. Cast without instanceofClassCastException.

Practice checkpoint

  1. What prints? Animal a = new Dog("X"); a.speak(); if Dog overrides speak.

    • Answer: Dog’s version (barks).
  2. Why call super(name) in Dog constructor?

    • Answer: Initialize inherited state in Animal before Dog-specific logic.
  3. When choose composition over extends?

    • Answer: Has-a relationship, need multiple behaviors, avoid fragile base class.
  4. Does @Override on wrong signature compile?

    • Answer: No—compiler error (unless matching overload accidentally).

Interview angles

  • “Compile-time vs runtime type?” — Variable declared type vs actual object type; overrides use runtime.
  • “Can you override private method?” — No; not visible to subclass (new method in child hides name only within child).
  • “Diamond problem?” — Java allows single class inheritance; interfaces use default methods with conflict rules.

Next: /java/learn/oop/abstract-classes-and-interfaces/