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
- Forgetting
super()when parent has no default constructor. - Overriding with weaker access — cannot reduce visibility (public → package-private).
- Calling overridable method from constructor — subclass override may run on partially constructed object.
- Deep inheritance for code reuse — leads to fragile base class; extract shared behavior to helper/composition.
- Cast without
instanceof—ClassCastException.
Practice checkpoint
-
What prints?
Animal a = new Dog("X"); a.speak();if Dog overrides speak.- Answer: Dog’s version (barks).
-
Why call
super(name)in Dog constructor?- Answer: Initialize inherited state in Animal before Dog-specific logic.
-
When choose composition over extends?
- Answer: Has-a relationship, need multiple behaviors, avoid fragile base class.
-
Does
@Overrideon 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.