Lesson 06 · 10 min read · Java for placements

Inheritance and polymorphism

extends and super, overriding against overloading, dynamic dispatch, upcasting, final, and why multiple class inheritance is out.

Inheritance is the mechanism. Polymorphism is the payoff. Most candidates can define both and cannot demonstrate the difference, so this lesson does it with code you could reproduce on a whiteboard.

extends

class Person {
    protected String name;

    Person(String name) {
        this.name = name;
    }

    String greet() {
        return "I am " + name;
    }
}

class Student extends Person {
    private String roll;

    Student(String name, String roll) {
        super(name);                 // MUST be the first statement
        this.roll = roll;
    }

    @Override
    String greet() {                 // overriding
        return super.greet() + ", roll " + roll;
    }
}
System.out.println(new Student("Raja", "05H4").greet());
// I am Raja, roll 05H4

super(name) calls the parent constructor. If you omit it, the compiler inserts super() — and if the parent has no no-argument constructor, that is a compile error. This is the single most common inheritance mistake.

super.greet() calls the parent's version of an overridden method, which is how you extend behaviour rather than replace it.

protected means visible to subclasses and to the same package. It is the access level that exists specifically for inheritance.

Overriding versus overloading

These two words look similar and mean unrelated things. Getting the distinction crisp is worth real marks.

Overriding Overloading
Where subclass redefines a parent method same class, several methods share a name
Signature must be identical must differ
Return type same, or a subtype (covariant) may be anything
Resolved at run time, by the object at compile time, by the arguments
Called dynamic dispatch / late binding static binding
Access cannot be more restrictive unrestricted
Applies to static no — that is hiding yes
class Parent {
    void show() { System.out.println("Parent"); }
    void show(int x) { System.out.println("Parent " + x); }   // overload
}

class Child extends Parent {
    @Override
    void show() { System.out.println("Child"); }              // override
}

Always write @Override. It is not decoration — it makes the compiler check that you really are overriding something. Misspell the method name, or get a parameter type wrong, and without the annotation you have silently added a new method while the parent's version keeps being called. That bug can survive a long time.

The rules for a valid override

  • Same name, same parameter types, in that order.
  • Return type identical, or a subtype of the parent's (covariant return).
  • Access may widen (protectedpublic) but never narrow (publicprotected is an error).
  • It may throw fewer or narrower checked exceptions, never broader ones. Unchecked exceptions are unrestricted.
  • private, static and final methods cannot be overridden.

Polymorphism, demonstrated

A parent-typed reference can hold a child object, and calling a method on it runs the child's version:

class Round {
    String marking() { return "not defined"; }
}

class Aptitude extends Round {
    @Override String marking() { return "1 mark, minus 0.25 negative"; }
}

class Coding extends Round {
    @Override String marking() { return "hidden test cases, partial credit"; }
}

class Interview extends Round {
    @Override String marking() { return "content, structure and delivery"; }
}
List<Round> drive = List.of(new Aptitude(), new Coding(), new Interview());

for (Round r : drive) {
    System.out.println(r.marking());     // each prints its own
}
// 1 mark, minus 0.25 negative
// hidden test cases, partial credit
// content, structure and delivery

The loop variable's type is Round. The method that runs is decided by the object, at run time. That is dynamic dispatch, and it is the whole point: adding a fourth round type requires no change to this loop.

The reference type still controls what you are allowed to call:

Round r = new Coding();
r.marking();            // fine — declared in Round
r.runTests();           // compile error, even though Coding has it
((Coding) r).runTests();    // downcast, now allowed

Upcasting (child → parent) is always safe and implicit. Downcasting (parent → child) is explicit and can fail at run time with ClassCastException, so guard it:

if (r instanceof Coding c) {      // Java 16+: tests and binds in one step
    c.runTests();
}

Fields are not polymorphic

A trap that appears in "what does this print" questions:

class Parent { String label = "parent"; }
class Child extends Parent { String label = "child"; }

Parent p = new Child();
System.out.println(p.label);            // "parent"  ← the REFERENCE type decides
System.out.println(((Child) p).label);  // "child"

Methods are dispatched on the object; fields are resolved on the reference type, at compile time. This is exactly why you keep fields private and expose them through methods.

Compile-time versus run-time polymorphism

Interviewers often ask for "the two types of polymorphism":

  • Compile-time (static polymorphism) — method overloading. The compiler picks by argument types.
  • Run-time (dynamic polymorphism) — method overriding. The JVM picks by the object's actual class.

Only the second is what most people mean by polymorphism, and only the second gives you the loop above.

final

Three uses, three different meanings:

final int MAX = 100;             // variable: cannot be reassigned
final void audit() { }           // method: cannot be overridden
final class Sealed { }           // class: cannot be extended

String, Integer and the other wrappers are final classes — you cannot subclass them, and their immutability therefore cannot be subverted.

An important subtlety: final on a reference stops reassignment, not mutation:

final List<String> names = new ArrayList<>();
names.add("Raja");               // allowed — the object changed
names = new ArrayList<>();       // compile error — the reference cannot

Why no multiple class inheritance

Java allows one superclass and any number of interfaces. The reason is the diamond problem:

      A
     / \
    B   C        both override doWork()
     \ /
      D          which doWork() does D inherit?

C++ allows this and resolves it with rules that are famously hard to reason about. Java's designers removed the ambiguity by removing the feature, and gave you interfaces instead — where multiple inheritance of type is allowed because there was no implementation to conflict.

Since Java 8, interfaces can have default methods, which reintroduces the possibility of a conflict — so the language added one rule: if two interfaces provide the same default method, the implementing class must override it and choose. Lesson 7 covers this.

Object, the universal parent

Every class implicitly extends Object, so every object has these:

toString()      // "ClassName@hashcode" unless you override it
equals(Object)  // reference equality unless you override it
hashCode()      // identity-based unless you override it
getClass()      // the runtime class
clone()         // protected; shallow copy
finalize()      // deprecated, never use
wait() notify() notifyAll()      // thread coordination

The three you should override deliberately are toString, equals and hashCode — lesson 11.

What to take into an interview

  • extends for one class; super(...) must be the first statement in a subclass constructor.
  • Overriding is run time and by the object; overloading is compile time and by the arguments.
  • Always write @Override so the compiler checks you.
  • An override may widen access and narrow checked exceptions, never the reverse.
  • Upcasting is implicit and safe; downcasting needs a cast and can throw ClassCastException.
  • Methods are polymorphic; fields are not — they resolve on the reference type.
  • final blocks reassignment, overriding or extension depending on where it sits.
  • No multiple class inheritance, because of the diamond problem; interfaces solve it.
  • Every class extends Object.
Lesson 6 of 12 · all 12 are free to read, no account. Course contents